stb-imv/imv.c

3368 lines
104 KiB
C
Raw Normal View History

/* stb(imv) windows image viewer
Copyright 2007 Sean Barrett
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
2007-06-30 04:25:15 +04:00
// Set section alignment to minimize alignment overhead
2007-06-25 07:45:23 +04:00
#pragma comment(linker, "/FILEALIGN:0x200")
#define _WIN32_WINNT 0x0500
//#define WINVER 0x0500 // produces an annoying warning message, so we do it the hard way
2007-06-25 07:45:23 +04:00
#include <windows.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <assert.h>
#define STB_DEFINE
#include "stb.h" /* http://nothings.org/stb.h */
2007-07-01 01:15:42 +04:00
#define STBI_FAILURE_USERMSG
#define STBI_NO_STDIO
2007-06-25 07:45:23 +04:00
#include "stb_image.c" /* http://nothings.org/stb_image.c */
2007-06-30 23:21:21 +04:00
#include "resource.h"
2007-06-25 07:45:23 +04:00
2007-07-11 21:09:01 +04:00
// general configuration options
#define USE_FREEIMAGE
// size of border in pixels
#define FRAME 3
// location within frame of secondary border
#define FRAME2 (FRAME >> 1)
// color of secondary border
#define GREY 192
2007-07-17 13:10:18 +04:00
int do_show;
int delay_time = 4000;
2007-07-11 21:09:01 +04:00
2007-06-27 01:22:01 +04:00
// all programs get the version number from the same place: version.bat
#define set static char *
#include "version.bat"
;
#undef set
2007-06-26 08:17:12 +04:00
2007-06-27 23:45:57 +04:00
// trivial error handling
2007-06-25 07:45:23 +04:00
void error(char *str) { MessageBox(NULL, str, "imv(stb) error", MB_OK); }
2007-06-27 23:45:57 +04:00
// OutputDebugString with varargs, can be compiled out
2007-06-26 05:00:05 +04:00
#ifdef _DEBUG
int do_debug=1;
2007-06-26 05:00:05 +04:00
void ods(char *str, ...)
2007-06-25 12:57:39 +04:00
{
if (do_debug) {
char buffer[1024];
va_list va;
va_start(va,str);
vsprintf(buffer, str, va);
va_end(va);
OutputDebugString(buffer);
}
2007-06-25 12:57:39 +04:00
}
2007-06-26 05:00:05 +04:00
#define o(x) ods x
#else
#define o(x)
#endif
2007-06-25 12:57:39 +04:00
2007-06-29 02:57:49 +04:00
// internal messages (all used for waking up main thread from tasks)
2007-06-25 12:57:39 +04:00
enum
{
WM_APP_DECODED = WM_APP,
2007-06-27 01:22:01 +04:00
WM_APP_LOAD_ERROR,
WM_APP_DECODE_ERROR,
2007-06-25 12:57:39 +04:00
};
2007-06-25 07:45:23 +04:00
2007-06-29 02:57:49 +04:00
// a few extra options for GetSystemMetrics for old compilers
2007-06-25 07:45:23 +04:00
#if WINVER < 0x0500
#define SM_XVIRTUALSCREEN 76
#define SM_YVIRTUALSCREEN 77
#define SM_CXVIRTUALSCREEN 78
#define SM_CYVIRTUALSCREEN 79
#define SM_CMONITORS 80
#define SM_SAMEDISPLAYFORMAT 81
#endif
char *displayName = "imv(stb)";
2007-06-29 02:57:49 +04:00
CHAR szAppName[] = "stb_imv";
HWND win;
2007-06-25 07:45:23 +04:00
2007-06-29 02:57:49 +04:00
// number of bytes per pixel (not bits); can be 3 or 4
2007-06-25 07:45:23 +04:00
#define BPP 4
2007-06-29 02:57:49 +04:00
// lightweight SetDIBitsToDevice() wrapper
// (once upon a time this was a platform-independent, hence the name)
2007-06-30 04:25:15 +04:00
// if 'dim' is set, draw it darkened
2007-06-26 08:17:12 +04:00
void platformDrawBitmap(HDC hdc, int x, int y, unsigned char *bits, int w, int h, int stride, int dim)
2007-06-25 07:45:23 +04:00
{
2007-06-26 08:17:12 +04:00
int i;
BITMAPINFOHEADER b = { sizeof(b) };
2007-06-25 07:45:23 +04:00
int result;
b.biPlanes=1;
b.biBitCount=BPP*8;
b.biWidth = stride/BPP;
b.biHeight = -h; // tell windows the bitmap is stored top-to-bottom
2007-06-30 04:25:15 +04:00
2007-06-26 08:17:12 +04:00
if (dim)
2007-06-30 04:25:15 +04:00
// divide the brightness of each channel by two... (if BPP==3, this
// does 4 pixels every 3 iterations)
2007-06-26 08:17:12 +04:00
for (i=0; i < stride*h; i += 4)
*(uint32 *)(bits+i) = (*(uint32 *)(bits+i) >> 1) & 0x7f7f7f7f;
2007-06-30 04:25:15 +04:00
2007-06-25 07:45:23 +04:00
result = SetDIBitsToDevice(hdc, x,y, w,abs(h), 0,0, 0,abs(h), bits, (BITMAPINFO *) &b, DIB_RGB_COLORS);
if (result == 0) {
DWORD e = GetLastError();
}
2007-06-30 04:25:15 +04:00
// bug: we restore by shifting, so we've discarded the bottom bit;
// thus, once you've viewed the help and come back, the display
// is slightly wrong until you resize or switch images. so we should
// probably save and restore it instead... slow, but we're displaying
// the help so no big deal?
2007-06-26 08:17:12 +04:00
if (dim)
for (i=0; i < stride*h; i += 4)
*(uint32 *)(bits+i) = (*(uint32 *)(bits+i) << 1);
2007-06-25 07:45:23 +04:00
}
2007-06-29 02:57:49 +04:00
// memory barrier for x86
2007-06-25 07:45:23 +04:00
void barrier(void)
{
long dummy;
__asm {
xchg dummy, eax
}
}
2007-06-29 02:57:49 +04:00
// awake the main thread when something interesting happens
void wake(int message)
{
PostMessage(win, message, 0,0);
}
2007-06-25 07:45:23 +04:00
typedef struct
{
2007-06-29 02:57:49 +04:00
int x,y; // size of the image
int stride; // distance between rows in bytes
int frame; // does this image have a frame (border)?
uint8 *pixels; // pointer to (0,0)th pixel
2007-06-30 23:21:21 +04:00
int had_alpha; // did this have alpha and we statically overwrote it?
2007-06-25 07:45:23 +04:00
} Image;
enum
{
2007-06-29 02:57:49 +04:00
// owned by main thread
2007-06-25 07:45:23 +04:00
LOAD_unused=0, // empty slot
LOAD_inactive, // filename slot, not loaded
2007-06-25 08:32:03 +04:00
2007-06-25 12:57:39 +04:00
// finished reading, needs decoding--originally decoder
// owned this, but then we couldn't free from the cache
LOAD_reading_done,
2007-06-25 08:32:03 +04:00
// in any of the following states, the image is as done as it can be
2007-06-25 07:45:23 +04:00
LOAD_error_reading,
LOAD_error_decoding,
2007-06-25 12:57:39 +04:00
LOAD_available, // loaded successfully
2007-06-25 07:45:23 +04:00
2007-06-29 02:57:49 +04:00
// owned by resizer
2007-06-25 07:45:23 +04:00
LOAD_resizing,
2007-06-29 02:57:49 +04:00
// owned by loader
2007-06-25 07:45:23 +04:00
LOAD_reading,
2007-06-29 02:57:49 +04:00
// owned by decoder
2007-06-25 07:45:23 +04:00
LOAD_decoding,
};
2007-06-29 02:57:49 +04:00
// does the main thread own this? (if this is true, the main
// thread can manipulate without locking, except for LOAD_reading_done
// which requires locking)
2007-06-25 07:45:23 +04:00
#define MAIN_OWNS(x) ((x)->status <= LOAD_available)
2007-06-29 02:57:49 +04:00
// data about a specific file
2007-06-25 07:45:23 +04:00
typedef struct
{
2007-06-29 02:57:49 +04:00
char *filename; // name of the file on disk, must be free()d
char *filedata; // data loaded from disk -- passed from reader to decoder
int len; // length of data loaded from disk -- as above
Image *image; // cached image -- passed from decoder to main
char *error; // error message -- from reader or decoder, must be free()d
int status; // current status/ownership with LOAD_* enum
int bail; // flag from main thread to work threads indicating to give up
int lru; // the larger, the higher priority--effectively a timestamp
2007-06-25 07:45:23 +04:00
} ImageFile;
2007-06-29 02:57:49 +04:00
// controls for interlocking communications
stb_mutex cache_mutex, decode_mutex;
stb_semaphore decode_queue;
2007-06-25 07:45:23 +04:00
stb_semaphore disk_command_queue;
stb_sync resize_merge;
2007-06-25 07:45:23 +04:00
2007-06-29 02:57:49 +04:00
// a request communicated from the main thread to the disk-loader task
2007-06-25 07:45:23 +04:00
typedef struct
{
int num_files;
ImageFile *files[4];
} DiskCommand;
2007-06-29 02:57:49 +04:00
// there can only be one pending command in flight
2007-06-25 07:45:23 +04:00
volatile DiskCommand dc_shared;
2007-06-29 02:57:49 +04:00
// the disk loader sits in this loop forever
2007-06-25 07:45:23 +04:00
void *diskload_task(void *p)
{
for(;;) {
int i;
DiskCommand dc;
2007-06-29 02:57:49 +04:00
// wait to be woken up by a command from the main thread
o(("READ: Waiting for disk request.\n"));
2007-06-25 07:45:23 +04:00
stb_sem_waitfor(disk_command_queue);
2007-06-29 02:57:49 +04:00
// it's possible for the main thread to do:
// 1. ... store a command in the command buffer ...
// 2. sem_release()
// 3. ... store a command in the command buffer ...
// 4. sem_release()
// and for this thread to complete a previous command and
// reach the waitfor() right after step 3 above. If this happens,
// this thread will pass the waitfor() (setting the semaphore to 0),
// process the latest command, the main thread will do step 4 (setting
// the semaphore to 1), and then this thread comes back around and
// passes the waitfor() again with no actual pending command. This
// case is handled below by clearing the command length to 0.
2007-06-25 07:45:23 +04:00
// grab the command; don't let the command or the cache change while we do it
stb_mutex_begin(cache_mutex);
2007-06-25 07:45:23 +04:00
{
2007-06-29 02:57:49 +04:00
// copy the command into a local buffer
2007-06-25 07:45:23 +04:00
dc = dc_shared;
2007-06-29 02:57:49 +04:00
// claim ownership over all the files in the command
2007-06-26 05:00:05 +04:00
for (i=0; i < dc.num_files; ++i) {
2007-06-25 07:45:23 +04:00
dc.files[i]->status = LOAD_reading;
2007-06-26 05:00:05 +04:00
assert(dc.files[i]->filedata == NULL);
}
2007-06-29 02:57:49 +04:00
// clear the command so we won't re-process it
2007-06-26 05:00:05 +04:00
dc_shared.num_files = 0;
2007-06-25 07:45:23 +04:00
}
stb_mutex_end(cache_mutex);
2007-06-25 07:45:23 +04:00
2007-06-29 02:57:49 +04:00
o(("READ: Got disk request, %d items.\n", dc.num_files));
2007-06-25 07:45:23 +04:00
for (i=0; i < dc.num_files; ++i) {
int n;
uint8 *data;
assert(dc.files[i]->status == LOAD_reading);
// check if the main thread changed its mind about this
2007-06-29 02:57:49 +04:00
// e.g. if this is the third file in a request, and the main thread
// has already made another command pending, then it will set this
// flag on previous requests, and we shouldn't waste time loading
// data that's no longer high-priority
2007-06-25 07:45:23 +04:00
if (dc.files[i]->bail) {
2007-06-29 02:57:49 +04:00
o(("READ: Bailing on disk request\n"));
2007-06-25 07:45:23 +04:00
dc.files[i]->status = LOAD_inactive;
} else {
2007-06-29 02:57:49 +04:00
o(("READ: Loading file %s\n", dc.files[i]->filename));
2007-06-26 05:00:05 +04:00
assert(dc.files[i]->filedata == NULL);
2007-06-29 02:57:49 +04:00
// read the data
2007-06-25 07:45:23 +04:00
data = stb_file(dc.files[i]->filename, &n);
2007-06-29 02:57:49 +04:00
// update the results
2007-06-25 07:45:23 +04:00
// don't need to mutex these, because we own them via ->status
if (data == NULL) {
2007-06-29 02:57:49 +04:00
o(("READ: error reading\n"));
2007-06-25 07:45:23 +04:00
dc.files[i]->error = strdup("can't open");
dc.files[i]->filedata = NULL;
2007-06-25 12:57:39 +04:00
dc.files[i]->len = 0;
2007-06-25 07:45:23 +04:00
barrier();
dc.files[i]->status = LOAD_error_reading;
2007-06-29 02:57:49 +04:00
wake(WM_APP_LOAD_ERROR); // wake main thread to react to error
2007-06-25 07:45:23 +04:00
} else {
2007-06-29 02:57:49 +04:00
o(("READ: Successfully read %d bytes\n", n));
2007-06-25 07:45:23 +04:00
dc.files[i]->error = NULL;
2007-06-25 12:57:39 +04:00
assert(dc.files[i]->filedata == NULL);
2007-06-25 07:45:23 +04:00
dc.files[i]->filedata = data;
dc.files[i]->len = n;
barrier();
dc.files[i]->status = LOAD_reading_done;
2007-06-29 02:57:49 +04:00
stb_sem_release(decode_queue); // wake the decode task if needed
2007-06-25 12:57:39 +04:00
}
2007-06-25 07:45:23 +04:00
}
}
}
}
2007-06-30 23:21:21 +04:00
static unsigned char alpha_background[2][3] =
{
{ 200,40,200 },
{ 150,30,150 },
};
2007-06-30 04:25:15 +04:00
// given raw decoded data from stbi_load, make it into a proper Image (e.g. creating a
// windows-compatible bitmap with 4-byte aligned rows and BGR color order)
2007-06-29 02:57:49 +04:00
void make_image(Image *z, int image_x, int image_y, uint8 *image_data, int image_n)
{
int i,j,k=0;
z->pixels = image_data;
z->x = image_x;
z->y = image_y;
z->stride = image_x*BPP;
z->frame = 0;
2007-06-30 23:21:21 +04:00
z->had_alpha = (image_n==4);
2007-06-29 02:57:49 +04:00
for (j=0; j < image_y; ++j) {
for (i=0; i < image_x; ++i) {
// swap RGB to BGR
unsigned char t = image_data[k+0];
image_data[k+0] = image_data[k+2];
image_data[k+2] = t;
2007-06-30 04:25:15 +04:00
#if BPP==4
2007-06-29 02:57:49 +04:00
// if image had an alpha channel, pre-blend with background
if (image_n == 4) {
unsigned char *p = image_data+k;
int a = (255-p[3]);
if ((i ^ j) & 8) {
2007-06-30 23:21:21 +04:00
p[0] += (((alpha_background[0][2] - (int) p[0])*a)>>8);
p[1] += (((alpha_background[0][1] - (int) p[1])*a)>>8);
p[2] += (((alpha_background[0][0] - (int) p[2])*a)>>8);
2007-06-29 02:57:49 +04:00
} else {
2007-06-30 23:21:21 +04:00
p[0] += (((alpha_background[1][2] - (int) p[0])*a)>>8);
p[1] += (((alpha_background[1][1] - (int) p[1])*a)>>8);
p[2] += (((alpha_background[1][0] - (int) p[2])*a)>>8);
2007-06-29 02:57:49 +04:00
}
}
#endif
k += BPP;
}
}
}
// Max entries in image cache. This shouldn't be TOO large, because we
// traverse it inside mutexes sometimes. Also, for large images, we'll
// hit cache-size limits fairly quickly (a 2 megapixel image requires
// 8MB, so you could only fit 50 in a 400MB cache), so no reason to be
// too large anyway
2007-06-25 07:45:23 +04:00
#define MAX_CACHED_IMAGES 200
2007-06-29 02:57:49 +04:00
// no idea if it needs to be volatile, decided not to worry about proving
// it one way or the other
2007-06-25 07:45:23 +04:00
volatile ImageFile cache[MAX_CACHED_IMAGES];
2007-06-29 02:57:49 +04:00
// choose which image to decode and claim ownership
2007-06-25 12:57:39 +04:00
volatile ImageFile *decoder_choose(void)
2007-06-25 07:45:23 +04:00
{
2007-06-25 12:57:39 +04:00
int i, best_lru=0;
volatile ImageFile *best = NULL;
2007-06-29 02:57:49 +04:00
// if we get unlucky we may have to bail and start over
2007-06-25 12:57:39 +04:00
start:
2007-06-29 02:57:49 +04:00
// iterate through the cache and find the ready-to-decode image
// that was most in demand (the highest priority will be the most-recently
// accessed image or, for prefetching, one right next to it; but this
// is policy determined by the main thread, not by this thread).
2007-06-25 12:57:39 +04:00
for (i=0; i < MAX_CACHED_IMAGES; ++i) {
if (cache[i].status == LOAD_reading_done) {
if (cache[i].lru > best_lru) {
best = &cache[i];
best_lru = best->lru;
2007-06-25 07:45:23 +04:00
}
}
2007-06-25 12:57:39 +04:00
}
2007-06-29 02:57:49 +04:00
// it's possible there is no image to decode; see the description
// in diskload_task of how it's possible for a task to be woken
// from the sem_release() without there being a pending command.
2007-06-25 12:57:39 +04:00
if (best) {
int retry = FALSE;
2007-06-29 02:57:49 +04:00
// if there is a best one, it's possible that while iterating
// it was flushed by the main thread. so let's make sure it's
// still ready to decode. (Of course it ALSO could have changed
2007-06-30 04:25:15 +04:00
// lru priority and other such, so not be the best anymore, but
2007-06-29 02:57:49 +04:00
// it's no big deal to get that wrong since it's close.)
stb_mutex_begin(cache_mutex);
2007-06-29 02:57:49 +04:00
{
if (best->status == LOAD_reading_done)
best->status = LOAD_decoding;
else
retry = TRUE;
}
stb_mutex_end(cache_mutex);
2007-06-29 02:57:49 +04:00
// if the status changed out from under us, try again
if (retry)
goto start;
2007-06-25 12:57:39 +04:00
}
return best;
}
2007-06-25 07:45:23 +04:00
2007-07-11 21:09:01 +04:00
static uint8 *imv_decode_from_memory(uint8 *mem, int len, int *x, int *y, int *n, int n_req);
static char *imv_failure_reason(void);
2007-07-11 21:09:01 +04:00
2007-06-25 12:57:39 +04:00
void *decode_task(void *p)
{
for(;;) {
2007-06-29 02:57:49 +04:00
// find the best image to decode
volatile ImageFile *f = decoder_choose();
2007-06-25 12:57:39 +04:00
if (f == NULL) {
2007-06-29 02:57:49 +04:00
// wait for load thread to wake us
o(("DECODE: blocking\n"));
2007-06-25 12:57:39 +04:00
stb_sem_waitfor(decode_queue);
2007-06-29 02:57:49 +04:00
o(("DECODE: woken\n"));
2007-06-25 07:45:23 +04:00
} else {
int x,y,n;
uint8 *data;
2007-06-25 12:57:39 +04:00
assert(f->status == LOAD_decoding);
2007-06-29 02:57:49 +04:00
// decode image
o(("DECIDE: decoding %s\n", f->filename));
2007-07-11 21:09:01 +04:00
data = imv_decode_from_memory(f->filedata, f->len, &x, &y, &n, BPP);
2007-06-29 02:57:49 +04:00
o(("DECODE: decoded %s\n", f->filename));
// free copy of data from disk, which we don't need anymore
2007-06-25 07:45:23 +04:00
free(f->filedata);
f->filedata = NULL;
2007-06-29 02:57:49 +04:00
2007-06-25 07:45:23 +04:00
if (data == NULL) {
2007-06-29 02:57:49 +04:00
// error reading file, record the reason for it
f->error = strdup(imv_failure_reason());
2007-06-25 07:45:23 +04:00
barrier();
f->status = LOAD_error_reading;
2007-06-29 02:57:49 +04:00
// wake up the main thread in case this is the most recent image
2007-06-27 01:22:01 +04:00
wake(WM_APP_DECODE_ERROR);
2007-06-25 07:45:23 +04:00
} else {
2007-06-29 02:57:49 +04:00
// post-process the image into the right format
2007-06-25 07:45:23 +04:00
f->image = (Image *) malloc(sizeof(*f->image));
make_image(f->image, x,y,data,n);
barrier();
f->status = LOAD_available;
2007-06-29 02:57:49 +04:00
// wake up the main thread in case this is the most recent image
2007-06-25 07:45:23 +04:00
wake(WM_APP_DECODED);
}
}
}
}
2007-06-29 02:57:49 +04:00
// the image cache entry currently trying to be displayed (may be waiting on resizer)
2007-06-25 07:45:23 +04:00
ImageFile *source_c;
2007-06-29 02:57:49 +04:00
// the image currently being displayed--historically redundant to source_c->image
Image *source;
2007-06-25 07:45:23 +04:00
2007-06-29 02:57:49 +04:00
// allocate an image in windows-friendly format
2007-06-25 07:45:23 +04:00
Image *bmp_alloc(int x, int y)
{
Image *i = malloc(sizeof(*i));
if (!i) return NULL;
i->x = x;
i->y = y;
i->stride = x*BPP;
i->stride += (-i->stride) & 3;
i->pixels = malloc(i->stride * i->y);
i->frame = 0;
2007-06-30 23:21:21 +04:00
i->had_alpha = 0;
2007-06-25 07:45:23 +04:00
if (i->pixels == NULL) { free(i); return NULL; }
return i;
}
2007-06-29 02:57:49 +04:00
// toggle for whether to draw the stripe in the middle of the border
2007-06-27 23:42:35 +04:00
int extra_border = TRUE;
2007-06-29 02:57:49 +04:00
// build the border into an image--this was easier than drawing it on
// the fly, although slightly less efficient, but probably totally
// redundant now that we paint an infinite black border around the image?
// reduces flickering of the stripe, I guess.
2007-06-25 07:45:23 +04:00
void frame(Image *z)
{
int i;
z->frame = FRAME;
memset(z->pixels, 0, FRAME*z->stride);
memset(z->pixels + z->stride*(z->y-FRAME), 0, FRAME*z->stride);
2007-06-27 23:42:35 +04:00
if (extra_border) {
2007-06-25 07:45:23 +04:00
memset(z->pixels + z->stride*FRAME2 + FRAME2*BPP, GREY, (z->x-FRAME2*2)*BPP);
memset(z->pixels + z->stride*(z->y-FRAME2-1) + FRAME2*BPP, GREY, (z->x-FRAME2*2)*BPP);
2007-06-27 23:42:35 +04:00
}
2007-06-25 07:45:23 +04:00
for (i=FRAME; i < z->y-FRAME; ++i) {
memset(z->pixels + i*z->stride, 0, FRAME*BPP);
memset(z->pixels + i*z->stride + (z->x-FRAME)*BPP, 0, FRAME*BPP);
}
2007-06-27 23:42:35 +04:00
if (extra_border) {
for (i=2; i < z->y-2; ++i) {
memset(z->pixels + i*z->stride+FRAME2*BPP, GREY, BPP);
memset(z->pixels + i*z->stride + (z->x-FRAME2-1)*BPP, GREY, BPP);
}
2007-06-25 07:45:23 +04:00
}
}
2007-06-30 04:25:15 +04:00
// free an image and its contents
2007-06-25 07:45:23 +04:00
void imfree(Image *x)
{
2007-06-27 01:22:01 +04:00
if (x) {
free(x->pixels);
free(x);
}
2007-06-25 07:45:23 +04:00
}
2007-06-29 02:57:49 +04:00
// return an Image which is a sub-region of another image
2007-06-25 07:45:23 +04:00
Image image_region(Image *p, int x, int y, int w, int h)
{
Image q;
q.stride = p->stride;
q.x = w;
q.y = h;
q.pixels = p->pixels + y*p->stride + x*BPP;
return q;
}
2007-06-29 02:57:49 +04:00
// the currently displayed image--may slightly lag source/source_c
// while waiting on a resize
2007-06-25 07:45:23 +04:00
Image *cur;
2007-06-29 02:57:49 +04:00
// the filename for the currently displayed image
2007-06-26 05:00:05 +04:00
char *cur_filename;
2007-06-26 08:17:12 +04:00
int show_help=0;
2007-06-30 04:25:15 +04:00
int downsample_cubic = 0;
int upsample_cubic = TRUE;
2007-06-26 08:17:12 +04:00
2007-07-15 14:47:50 +04:00
int cur_loc = -1; // offset within the current list of files
// information about files we have currently loaded
struct
{
char *filename;
int lru;
} *fileinfo;
2007-06-30 04:25:15 +04:00
// declare with extra bytes so we can print the version number into it
char helptext_center[140] =
2007-06-27 23:42:35 +04:00
"imv(stb)\n"
2007-07-10 13:48:36 +04:00
"Copyright 2007 Sean Barrett\n"
2007-06-27 23:42:35 +04:00
"http://code.google.com/p/stb-imv\n"
"version "
2007-06-26 08:17:12 +04:00
;
char helptext_left[] =
"\n\n\n\n\n\n"
2007-06-30 23:21:21 +04:00
" ALT-ENTER: toggle size\n"
" CTRL-PLUS: zoom in\n"
2007-06-26 08:17:12 +04:00
"CTRL-MINUS: zoom out\n"
2007-06-30 04:25:15 +04:00
"RIGHT, SPACE: next image\n"
"LEFT, BACKSPACE: previous image\n"
2007-07-15 14:47:50 +04:00
" O: open image\n"
" B: toggle border\n"
"SHIFT-B: toggle border but keep stripe\n"
" CTRL-B: toggle white stripe in border\n"
2007-06-30 23:21:21 +04:00
" L: toggle filename label\n"
2007-06-26 08:17:12 +04:00
;
char helptext_right[] =
"\n\n\n\n\n\n\n"
2007-06-26 08:17:12 +04:00
"right-click to exit\n"
"left drag center to move\n"
"left drag edges to resize\n"
"double-click to toggle size\n"
2007-06-27 23:42:35 +04:00
"mousewheel to zoom\n"
2007-06-26 08:17:12 +04:00
"\n"
2007-07-15 14:47:50 +04:00
"F1, H, ?: help\n"
"ESC: exit\n"
"P: change preferences\n"
"CTRL-C: copy filename to clipboard\n"
2007-07-17 13:10:18 +04:00
"CTRL-I: launch new viewer instance\n"
2007-06-26 08:17:12 +04:00
;
2007-06-29 02:57:49 +04:00
// draw the help text semi-prettily
// originally this was to try to avoid having to darken the image
// that it's rendered over, but I couldn't make that work, and with
// the darkened image there's no real need to do this, but hey, it
// looks a little nicer so why not
2007-06-26 08:17:12 +04:00
void draw_nice(HDC hdc, char *text, RECT *rect, uint flags)
{
int i,j;
SetTextColor(hdc, RGB(80,80,80));
for (i=2; i >= 1; i -= 1)
for (j=2; j >= 1; j -= 1)
{
2007-06-29 02:57:49 +04:00
// displace the rectangle so as to displace the text
2007-06-26 08:17:12 +04:00
RECT r = { rect->left+i, rect->top+j, rect->right+i, rect->bottom + j };
if (i == 1 && j == 1)
SetTextColor(hdc, RGB(0,0,0));
DrawText(hdc, text, -1, &r, flags);
}
SetTextColor(hdc, RGB(255,255,255));
DrawText(hdc, text, -1, rect, flags);
}
2007-06-25 07:45:23 +04:00
2007-06-29 02:57:49 +04:00
// cached error message for most recent image
2007-06-27 01:22:01 +04:00
char display_error[1024];
2007-06-29 02:57:49 +04:00
// to make an image with an error the most recent image, call this
2007-06-27 01:22:01 +04:00
void set_error(volatile ImageFile *z)
{
sprintf(display_error, "File:\n%s\nError:\n%s\n", z->filename, z->error);
InvalidateRect(win, NULL, FALSE);
imfree(cur);
cur = NULL;
free(cur_filename);
cur_filename = strdup(z->filename);
source_c = (ImageFile *) z;
source = NULL;
}
2007-06-27 23:42:35 +04:00
HFONT label_font;
int label_font_height=12;
// build the font for the filename label
void build_label_font(void)
{
LOGFONT lf = {0};
lf.lfHeight = label_font_height;
lf.lfOutPrecision = OUT_TT_PRECIS; // prefer truetype to raster fonts
strcpy(lf.lfFaceName, "Times New Roman");
if (label_font) DeleteObject(label_font);
label_font = CreateFontIndirect(&lf);
}
2007-06-29 02:57:49 +04:00
int show_frame = TRUE; // show border or not?
2007-06-30 04:25:15 +04:00
int show_label = FALSE; // display the help text or not
// WM_PAINT, etc.
2007-06-25 07:45:23 +04:00
void display(HWND win, HDC hdc)
{
RECT rect,r2;
HBRUSH b = GetStockObject(BLACK_BRUSH);
int w,h,x,y;
2007-06-29 02:57:49 +04:00
// get the window size for centering
2007-06-25 07:45:23 +04:00
GetClientRect(win, &rect);
w = rect.right - rect.left;
h = rect.bottom - rect.top;
2007-06-29 02:57:49 +04:00
// set the text rendering mode for our fancy text
2007-06-27 01:22:01 +04:00
SetBkMode(hdc, TRANSPARENT);
2007-06-29 02:57:49 +04:00
// if the current image had an error, just display that
2007-06-27 01:22:01 +04:00
if (display_error[0]) {
2007-06-29 02:57:49 +04:00
FillRect(hdc, &rect, b); // clear to black -- will flicker
if (rect.bottom > rect.top + 100) rect.top += 50; // displace down from top; could center
2007-06-27 01:22:01 +04:00
draw_nice(hdc, display_error, &rect, DT_CENTER);
return;
}
2007-06-26 08:17:12 +04:00
2007-06-29 02:57:49 +04:00
// because show_frame toggles the window size, and we center the bitmap,
// we just go ahead and render the entire bitmap with the border in it
// regardless of the show_frame toggle. You can see that when you resize
// a window in one dimension--the strip is still there, just off the edge
// of the window.
2007-06-25 07:45:23 +04:00
x = (w - cur->x) >> 1;
y = (h - cur->y) >> 1;
2007-06-26 08:17:12 +04:00
platformDrawBitmap(hdc, x,y,cur->pixels, cur->x, cur->y, cur->stride, show_help);
2007-06-29 02:57:49 +04:00
// draw in infinite borders on all four sides
2007-06-25 07:45:23 +04:00
r2 = rect;
r2.right = x; FillRect(hdc, &r2, b); r2=rect;
r2.left = x + cur->x; FillRect(hdc, &r2, b); r2 = rect;
r2.left = x;
r2.right = x+cur->x;
r2.bottom = y; FillRect(hdc, &r2, b); r2 = rect;
r2.left = x;
r2.right = x+cur->x;
r2.top = y + cur->y; FillRect(hdc, &r2, b);
2007-06-26 08:17:12 +04:00
2007-06-30 04:25:15 +04:00
// should we show the name of the file?
2007-06-27 23:42:35 +04:00
if (show_label) {
SIZE size;
RECT z;
HFONT old = NULL;
2007-07-15 14:47:50 +04:00
char buffer[1024];
2007-06-27 23:42:35 +04:00
char *name = cur_filename ? cur_filename : "(none)";
2007-07-15 14:47:50 +04:00
if (fileinfo) {
sprintf(buffer, "%s ( %d / %d )", name, cur_loc+1, stb_arr_len(fileinfo));
name = buffer;
}
2007-06-27 23:42:35 +04:00
if (label_font) old = SelectObject(hdc, label_font);
2007-06-29 02:57:49 +04:00
// get rect around label so we can draw it ourselves, because
2007-06-30 04:25:15 +04:00
// the DrawText() one is poorly sized
2007-06-29 02:57:49 +04:00
2007-06-27 23:42:35 +04:00
GetTextExtentPoint32(hdc, name, strlen(name), &size);
z.left = rect.left+1;
z.bottom = rect.bottom+1;
z.top = z.bottom - size.cy - 4;
z.right = z.left + size.cx + 10;
FillRect(hdc, &z, b);
2007-06-29 02:57:49 +04:00
z.bottom -= 2; // extra padding on bottom because it's at edge of window
2007-06-27 23:42:35 +04:00
SetTextColor(hdc, RGB(255,255,255));
DrawText(hdc, name, -1, &z, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
if (old) SelectObject(hdc, old);
}
2007-06-26 08:17:12 +04:00
if (show_help) {
int h2;
RECT box = rect;
2007-06-29 02:57:49 +04:00
// measure height of longest text
2007-06-26 08:17:12 +04:00
DrawText(hdc, helptext_left, -1, &box, DT_CALCRECT);
h2 = box.bottom - box.top;
2007-06-29 02:57:49 +04:00
// build rect of correct height
2007-06-26 08:17:12 +04:00
box = rect;
box.top = stb_max((h - h2) >> 1, 0);
2007-06-30 04:25:15 +04:00
//box.bottom = box.top + h2;
2007-06-29 02:57:49 +04:00
// expand on left & right so following code is well behaved
2007-06-30 04:25:15 +04:00
// (we're centered anyway, so the exact numbers don't matter)
2007-06-29 02:57:49 +04:00
box.left -= 200; box.right += 200;
// draw center text
2007-06-26 08:17:12 +04:00
draw_nice(hdc, helptext_center, &box, DT_CENTER);
2007-06-29 02:57:49 +04:00
// displace box to left and draw left column
2007-06-27 23:42:35 +04:00
box.left -= 150; box.right -= 150;
2007-06-26 08:17:12 +04:00
draw_nice(hdc, helptext_left, &box, DT_CENTER);
2007-06-29 02:57:49 +04:00
// displace box to right and draw right column
2007-06-27 23:42:35 +04:00
box.left += 300; box.right += 300;
2007-06-26 08:17:12 +04:00
draw_nice(hdc, helptext_right, &box, DT_CENTER);
}
2007-06-25 07:45:23 +04:00
}
typedef struct
{
int x,y;
int w,h;
} queued_size;
2007-06-29 02:57:49 +04:00
// most recent unsatisfied resize request (private to main thread)
2007-06-25 07:45:23 +04:00
queued_size qs;
2007-06-29 02:57:49 +04:00
// active resize request, mainly just used by main thread (resize
// thread writes to 'image' field.
2007-06-25 07:45:23 +04:00
struct
{
queued_size size;
Image *image;
2007-06-26 05:00:05 +04:00
char *filename;
2007-06-29 14:18:49 +04:00
ImageFile *image_c;
2007-06-25 07:45:23 +04:00
} pending_resize;
2007-06-29 02:57:49 +04:00
// temporary structure for communicating across stb_workq() call
2007-06-25 07:45:23 +04:00
typedef struct
{
ImageFile *src;
Image dest;
Image *result;
} Resize;
2007-06-29 02:57:49 +04:00
// threaded image resizer, uses work queue AND current thread
2007-07-17 13:10:18 +04:00
void image_resize(Image *dest, Image *src);
2007-06-25 07:45:23 +04:00
2007-06-29 02:57:49 +04:00
// wrapper for image_resize() to be called via work queue
2007-06-25 07:45:23 +04:00
void * work_resize(void *p)
{
Resize *r = (Resize *) p;
2007-06-29 14:18:49 +04:00
image_resize(&r->dest, r->src->image);
2007-06-25 07:45:23 +04:00
return r->result;
}
2007-06-29 02:57:49 +04:00
// dedicate workqueue workers for resizing
2007-06-25 07:45:23 +04:00
stb_workqueue *resize_workers;
2007-06-29 02:57:49 +04:00
// compute the size to resize an image to given a target window (gw,gh);
// we assume the input window (sw,wh) has already been expanded by its
// frame size.
2007-06-25 07:45:23 +04:00
void compute_size(int gw, int gh, int sw, int sh, int *ox, int *oy)
{
2007-06-29 02:57:49 +04:00
// shrink the target by the padding (the size of the frame)
2007-06-25 07:45:23 +04:00
gw -= FRAME*2;
gh -= FRAME*2;
2007-06-29 02:57:49 +04:00
// shrink the source to remove the frame
2007-06-25 07:45:23 +04:00
sw -= FRAME*2;
sh -= FRAME*2;
2007-06-29 02:57:49 +04:00
// compute the raw pixel resize
2007-06-25 07:45:23 +04:00
if (gw*sh > gh*sw) {
*oy = gh;
*ox = gh * sw/sh;
} else {
*ox = gw;
*oy = gw * sh/sw;
}
}
2007-06-29 02:57:49 +04:00
// resize an image. if immediate=TRUE, we run it from the main thread
// and won't return until it's resized; if !immediate, we hand it to
// a workqueue and return before it's done. (note that if immediate=TRUE,
// we still use the work queue to accelerate, if possible)
2007-06-25 07:45:23 +04:00
void queue_resize(int w, int h, ImageFile *src_c, int immediate)
{
2007-06-29 02:57:49 +04:00
static Resize res; // must be static because we expose (very briefly) to other thread
2007-06-25 07:45:23 +04:00
Image *src = src_c->image;
Image *dest;
int w2,h2;
if (!immediate) assert(pending_resize.size.w);
2007-06-27 01:22:01 +04:00
if (src_c == NULL) return;
2007-06-25 07:45:23 +04:00
// create (w2,h2) matching aspect ratio of w/h
2007-06-29 02:57:49 +04:00
compute_size(w,h,src->x+FRAME*2,src->y+FRAME*2,&w2,&h2);
// create output of the appropriate size
dest = bmp_alloc(w2+FRAME*2,h2+FRAME*2);
assert(dest);
if (!dest) return;
// encode the border around it
2007-06-25 07:45:23 +04:00
frame(dest);
2007-06-29 02:57:49 +04:00
// build the parameter list for image_resize
2007-06-25 07:45:23 +04:00
res.src = src_c;
res.dest = image_region(dest, FRAME, FRAME, w2, h2);
res.result = dest;
if (!immediate) {
2007-06-29 02:57:49 +04:00
// update status to be owned by the resizer (which isn't running yet,
// so there's no thread issues here)
2007-06-25 07:45:23 +04:00
src_c->status = LOAD_resizing;
2007-06-29 02:57:49 +04:00
// store data to come back for later
2007-06-25 07:45:23 +04:00
pending_resize.image = NULL;
2007-06-29 14:18:49 +04:00
pending_resize.image_c = src_c;
2007-06-26 05:00:05 +04:00
pending_resize.filename = strdup(src_c->filename);
2007-06-29 02:57:49 +04:00
// run the resizer in the background (equivalent to the call below)
2007-06-25 07:45:23 +04:00
stb_workq(resize_workers, work_resize, &res, &pending_resize.image);
} else {
2007-06-29 02:57:49 +04:00
// run the resizer in the main thread
pending_resize.image = work_resize(&res);
2007-06-25 07:45:23 +04:00
}
}
2007-06-29 02:57:49 +04:00
// put a resize request in the "queue" (which is only one deep)
2007-06-25 07:45:23 +04:00
void enqueue_resize(int left, int top, int width, int height)
{
2007-06-27 01:22:01 +04:00
if (cur && ((width == cur->x && height >= cur->y) || (height == cur->y && width >= cur->x))) {
2007-06-29 02:57:49 +04:00
// if we have a current image, and that image can satisfy the request (they're
// dragging one side of the image out wider), just immediately update the window
qs.w = 0; // clear the queue
2007-07-17 13:10:18 +04:00
if (!show_frame) left += FRAME, top += FRAME, width -= 2*FRAME, height -= 2*FRAME;
2007-06-25 07:45:23 +04:00
MoveWindow(win, left, top, width, height, TRUE);
InvalidateRect(win, NULL, FALSE);
} else {
2007-06-29 02:57:49 +04:00
// otherwise store the most recent request for processing in the main thread
2007-06-25 07:45:23 +04:00
qs.x = left;
qs.y = top;
qs.w = width;
qs.h = height;
}
}
2007-06-27 23:42:35 +04:00
// do all operations _as if_ we had the frame
void GetAdjustedWindowRect(HWND win, RECT *rect)
{
GetWindowRect(win, rect);
if (!show_frame) {
rect->left -= FRAME;
rect->top -= FRAME;
rect->right += FRAME;
rect->bottom += FRAME;
}
}
2007-06-30 04:25:15 +04:00
// compute the size we'd prefer this window to be at for 1:1-ness
void ideal_window_size(int w, int h, int *w_ideal, int *h_ideal, int *x, int *y)
{
// @TODO: this probably isn't right if the virtual TL isn't (0,0)???
int cx = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int cy = GetSystemMetrics(SM_CYVIRTUALSCREEN);
int cx2 = GetSystemMetrics(SM_CXSCREEN);
int cy2 = GetSystemMetrics(SM_CYSCREEN);
if (w <= cx2 && h <= cy2) {
// if the image fits on the primary monitor, go for it
*w_ideal = w;
*h_ideal = h;
} else if (w - FRAME*2 <= cx2 && h - FRAME*2 <= cy2) {
// if the image fits on the primary monitor with border...
// this makes the test above irrelevant
*w_ideal = w;
*h_ideal = h;
} else {
// will we show more if we use the virtual desktop, rather than just the primary?
int w1,h1,w2,h2;
compute_size(cx+FRAME*2 ,cy+FRAME*2,w,h,&w1,&h1);
compute_size(cx2+FRAME*2,cy2+FRAME*2,w,h,&w2,&h2);
// require it be "significantly more" on the virtual
if (h1 > h2*1.25 || w1 > w2*1.25) {
*w_ideal = stb_min(cx,w1)+FRAME*2;
*h_ideal = stb_min(cy,h1)+FRAME*2;
} else {
*w_ideal = stb_min(cx2,w2)+FRAME*2;
*h_ideal = stb_min(cy2,h2)+FRAME*2;
}
// compute actual size image will be if fit to this window
compute_size(*w_ideal, *h_ideal, w,h, &w,&h);
// and add the padding in
w += FRAME*2;
h += FRAME*2;
}
// now find center point...
if ((cx != cx2 || cy != cy2) && w <= cx2+FRAME*2 && h <= cy2+FRAME*2) {
// if it fits on the primary, center it on the primary
*x = (cx2 - w) >> 1;
*y = (cy2 - h) >> 1;
} else {
// otherwise center on the virtual
*x = (cx - w) >> 1;
*y = (cy - h) >> 1;
}
}
2007-06-25 07:45:23 +04:00
2007-06-26 05:00:05 +04:00
enum
{
2007-06-30 04:25:15 +04:00
DISPLAY_actual, // display the image 1:1, or fullscreen if larger than screen
DISPLAY_current, // display the image in the current window's size
2007-06-26 05:00:05 +04:00
DISPLAY__num,
};
int display_mode;
2007-06-30 04:25:15 +04:00
// resize the current image to match the current window/mode, adjusting
// the window in mode DISPLAY_actual. If 'maximize' and the mode is
// DISPLAY_current, it means they've // double-clicked or alt-entered
// into 'fullscreen', so we want to maximize the window.
2007-06-26 05:00:05 +04:00
void size_to_current(int maximize)
2007-06-25 07:45:23 +04:00
{
int w2,h2;
int w,h,x,y;
2007-06-30 04:25:15 +04:00
// the 1:1 actual size WITH frame
w2 = source->x+FRAME*2;
h2 = source->y+FRAME*2;
2007-06-26 05:00:05 +04:00
switch (display_mode) {
2007-06-27 01:22:01 +04:00
case DISPLAY_actual: {
int cx,cy;
RECT rect;
2007-06-30 04:25:15 +04:00
// given the actual size, compute the ideal window size
// (which is either 1:1 or fullscreen) and center point
2007-06-26 05:00:05 +04:00
ideal_window_size(w2,h2, &w,&h, &x,&y);
2007-06-30 04:25:15 +04:00
// get the desktop size
2007-06-27 01:22:01 +04:00
cx = GetSystemMetrics(SM_CXSCREEN);
cy = GetSystemMetrics(SM_CYSCREEN);
2007-06-30 04:25:15 +04:00
// if the window fits on the desktop
2007-06-27 01:22:01 +04:00
if (w <= cx && h <= cy) {
2007-06-30 04:25:15 +04:00
// try to use the current center point, as much as possible
2007-06-27 23:42:35 +04:00
GetAdjustedWindowRect(win, &rect);
2007-06-27 01:22:01 +04:00
x = (rect.right + rect.left - w) >> 1;
y = (rect.top + rect.bottom - h) >> 1;
x = stb_clamp(x,0,cx-w);
y = stb_clamp(y,0,cy-h);
}
2007-06-26 05:00:05 +04:00
break;
2007-06-27 01:22:01 +04:00
}
2007-06-30 04:25:15 +04:00
2007-06-26 05:00:05 +04:00
case DISPLAY_current:
if (maximize) {
2007-06-30 04:25:15 +04:00
// fullscreen, plus the frame around the edge
2007-06-26 05:00:05 +04:00
x = y = -FRAME;
w = GetSystemMetrics(SM_CXSCREEN) + FRAME*2;
h = GetSystemMetrics(SM_CYSCREEN) + FRAME*2;
} else {
2007-06-30 04:25:15 +04:00
// just use the current window
2007-06-26 05:00:05 +04:00
RECT rect;
2007-06-27 23:42:35 +04:00
GetAdjustedWindowRect(win, &rect);
2007-06-26 05:00:05 +04:00
x = rect.left;
y = rect.top;
w = rect.right - rect.left;
h = rect.bottom - rect.top;
}
break;
}
2007-06-25 07:45:23 +04:00
2007-06-30 04:25:15 +04:00
// if the image is 1:1, we don't need to resize, so skip
// queueing and all that and just build it
2007-06-26 05:00:05 +04:00
if (w == w2 && h == h2) {
2007-06-25 07:45:23 +04:00
int j;
2007-06-30 04:25:15 +04:00
unsigned char *p = source->pixels;
// free the current image
2007-06-25 12:57:39 +04:00
imfree(cur);
2007-06-26 05:00:05 +04:00
free(cur_filename);
2007-06-30 04:25:15 +04:00
// build the new one
cur = bmp_alloc(w2,h2);
2007-06-26 05:00:05 +04:00
cur_filename = strdup(source_c->filename);
2007-06-30 04:25:15 +04:00
// build a frame around the data
2007-06-25 07:45:23 +04:00
frame(cur);
2007-06-30 04:25:15 +04:00
// copy the raw data in
for (j=0; j < source->y; ++j) {
unsigned char *q = cur->pixels + (j+FRAME)*cur->stride + FRAME*BPP;
memcpy(q, p, source->x*BPP);
p += source->x*BPP;
2007-06-25 07:45:23 +04:00
}
2007-06-30 04:25:15 +04:00
// no error for this image
display_error[0] = 0;
// if they don't want the frame, remove it now
2007-06-27 23:42:35 +04:00
if (!show_frame) x+=FRAME,y+=FRAME,w-=FRAME*2,h-=FRAME*2;
2007-06-30 04:25:15 +04:00
// move/show it
2007-06-25 07:45:23 +04:00
MoveWindow(win, x,y,w,h, TRUE);
InvalidateRect(win, NULL, FALSE);
} else {
2007-06-30 04:25:15 +04:00
// not 1:1; it requires resizing, so queue a resize request
2007-06-25 07:45:23 +04:00
qs.x = x;
qs.y = y;
qs.w = w;
qs.h = h;
}
}
2007-06-30 04:25:15 +04:00
// when the user toggles the frame on and off, toggle the flag
// and update the window size as appropriate
2007-06-27 23:42:35 +04:00
void toggle_frame(void)
{
RECT rect;
show_frame = !show_frame;
GetWindowRect(win, &rect);
if (show_frame) {
rect.left -= FRAME;
rect.right += FRAME;
rect.top -= FRAME;
rect.bottom += FRAME;
} else {
rect.left += FRAME;
rect.right -= FRAME;
rect.top += FRAME;
rect.bottom -= FRAME;
}
SetWindowPos(win, NULL, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, SWP_NOCOPYBITS|SWP_NOOWNERZORDER);
}
2007-06-30 04:25:15 +04:00
// the most recent image we've seen
int best_lru = 0;
// when we change which file is the one being viewed/resized,
// call this function to update our globals and fit to window
2007-06-26 05:00:05 +04:00
void update_source(ImageFile *q)
{
2007-06-30 04:25:15 +04:00
source = q->image;
2007-06-26 05:00:05 +04:00
source_c = q;
2007-06-30 04:25:15 +04:00
o(("Making %s (%d) current\n", q->filename, q->lru));
if (q->lru > best_lru)
best_lru = q->lru;
2007-06-26 05:00:05 +04:00
2007-06-30 04:25:15 +04:00
if (source)
size_to_current(FALSE); // don't maximize
2007-06-26 05:00:05 +04:00
}
2007-06-30 04:25:15 +04:00
// toggle between the two main display modes
2007-06-26 05:00:05 +04:00
void toggle_display(void)
{
2007-06-27 01:22:01 +04:00
if (source) {
display_mode = (display_mode + 1) % DISPLAY__num;
2007-06-30 04:25:15 +04:00
size_to_current(TRUE); // _DO_ maximize if DISPLAY_current
2007-06-27 01:22:01 +04:00
}
2007-06-26 05:00:05 +04:00
}
2007-06-30 04:25:15 +04:00
// manage the list of files in the current directory
// note that this is totally detached from the image cache;
// if you switch directories, the cache will still have
// images from the old directory, and if you switch back
// before they're flushed, it will still be valid
char path_to_file[4096];
char *filename; // @TODO: gah, we have cur_filename AND filename. and filename is being set dumbly!
// stb_sdict is a string dictionary (strings as keys, void * as values)
// dictionary mapping filenames (key) to cached images (ImageFile *)
// We can't just replace this with an ImageFile* in the fileinfo (and
// the backpointer) because we keep ImageFile entries around for images
// not in the fileinfo list.
2007-06-25 07:45:23 +04:00
stb_sdict *file_cache;
2007-06-30 04:25:15 +04:00
// when switching/refreshing directories, free this data
void free_fileinfo(void)
{
int i;
for (i=0; i < stb_arr_len(fileinfo); ++i)
2007-06-30 04:25:15 +04:00
free(fileinfo[i].filename); // allocated by stb_readdir
stb_arr_free(fileinfo);
fileinfo = NULL;
}
2007-07-15 14:47:50 +04:00
//derived from michael herf's code: http://www.stereopsis.com/strcmp4humans.html
// sorts like this:
// foo.jpg
// foo1.jpg
// foo2.jpg
// foo10.jpg
// foo_1.jpg
// food.jpg
// use upper, not lower, to get better sorting versus '_'
__forceinline char tupper(char b)
{
//if (b >= 'A' && b <= 'Z') return b - 'A' + 'a';
if (b >= 'a' && b <= 'z') return b - 'a' + 'A';
return b;
}
__forceinline char isnum(char b)
{
if (b >= '0' && b <= '9') return 1;
return 0;
}
__forceinline int parsenum(char **a_p)
{
char *a = *a_p;
int result = *a - '0';
++a;
while (isnum(*a)) {
result *= 10;
result += *a - '0';
++a;
}
*a_p = a-1;
return result;
}
int StringCompare(char *a, char *b)
{
char *orig_a = a, *orig_b = b;
if (a == b) return 0;
if (a == NULL) return -1;
if (b == NULL) return 1;
while (*a && *b) {
int a0, b0; // will contain either a number or a letter
if (isnum(*a) && isnum(*b)) {
a0 = parsenum(&a);
b0 = parsenum(&b);
} else {
// if they are mixed number and character, use ASCII comparison
// order between them (number before character), not herf's
// approach (numbers after everything else). this produces the order:
// foo.jpg
// foo1.jpg
// food.jpg
// foo_.jpg
// which I think looks better than having foo_ before food (but
// I could be wrong, given how a blank space sorts)
a0 = tupper(*a);
b0 = tupper(*b);
}
if (a0 < b0) return -1;
if (a0 > b0) return 1;
++a;
++b;
}
if (*a) return 1;
if (*b) return -1;
{
// if strings differ only by leading 0s, use case-insensitive ASCII sort
// (note, we should work this out more efficiently by noticing which one changes length first)
int z = stricmp(orig_a, orig_b);
if (z) return z;
// if identical case-insensitive, return ASCII sort
return strcmp(orig_a, orig_b);
}
}
int StringCompareSort(const void *p, const void *q)
{
return StringCompare(*(char **) p, *(char **) q);
}
2007-07-11 21:09:01 +04:00
char *open_filter = "Image Files\0*.jpg;*.jpeg;*.png;*.bmp\0";
2007-06-30 04:25:15 +04:00
// build a filelist for the current directory
2007-06-25 07:45:23 +04:00
void init_filelist(void)
{
2007-06-30 04:25:15 +04:00
char **image_files; // stb_arr (dynamic array type) of filenames
char *to_free = NULL;
2007-06-25 07:45:23 +04:00
int i;
if (fileinfo) {
2007-06-30 04:25:15 +04:00
// cache the current filename so we can look for it in the list below
// @BUG: is this leaking the old filename?
filename = to_free = strdup(fileinfo[cur_loc].filename);
free_fileinfo();
2007-06-25 07:45:23 +04:00
}
2007-07-11 21:09:01 +04:00
image_files = stb_readdir_files_mask(path_to_file, open_filter + 12);
2007-07-10 13:48:36 +04:00
if (image_files == NULL) { error("Error: couldn't read directory."); exit(0); }
2007-07-15 14:47:50 +04:00
qsort(image_files, stb_arr_len(image_files), sizeof(*image_files), StringCompareSort);
2007-06-25 07:45:23 +04:00
2007-06-30 04:25:15 +04:00
// given the array of filenames, build an equivalent fileinfo array
2007-06-25 07:45:23 +04:00
stb_arr_setlen(fileinfo, stb_arr_len(image_files));
2007-06-30 04:25:15 +04:00
// while we're going through, let's look for the current file, and
// initialize 'cur_loc' to that value. Otherwise it gets a 0.
cur_loc = 0;
2007-06-25 07:45:23 +04:00
for (i=0; i < stb_arr_len(image_files); ++i) {
fileinfo[i].filename = image_files[i];
fileinfo[i].lru = 0;
if (!stricmp(image_files[i], filename))
cur_loc = i;
}
2007-06-30 04:25:15 +04:00
// if we made a temp copy of the filename, free it... wait, why,
// given that we're not setting filename=NULL?!
// @TODO: why didn't this hurt? if (to_free) free(to_free);
2007-06-30 04:25:15 +04:00
// free the stb_readdir() array, but not the filenames themselves
stb_arr_free(image_files);
}
// current lru timestamp
2007-06-25 07:45:23 +04:00
int lru_stamp=1;
2007-06-30 04:25:15 +04:00
// maximum size of the cache
2007-06-25 12:57:39 +04:00
int max_cache_bytes = 256 * (1 << 20); // 256 MB; one 5MP image is 20MB
2007-06-30 04:25:15 +04:00
// minimum number of cache entries
2007-06-25 12:57:39 +04:00
#define MIN_CACHE 3 // always keep 3 images cached, to allow prefetching
2007-06-30 04:25:15 +04:00
// compare the lru timestamps in two cached images, with extra indirection
2007-06-25 12:57:39 +04:00
int ImageFilePtrCompare(const void *p, const void *q)
{
ImageFile *a = *(ImageFile **) p;
ImageFile *b = *(ImageFile **) q;
return (a->lru < b->lru) ? -1 : (a->lru > b->lru);
}
2007-06-25 07:45:23 +04:00
2007-06-30 04:25:15 +04:00
// see if we should flush any data. we should flush if
// (a) there aren't enough free slots for prefetching, and
// (b) if we're using too much memory
2007-06-25 12:57:39 +04:00
void flush_cache(int locked)
2007-06-25 07:45:23 +04:00
{
2007-06-30 04:25:15 +04:00
int limit = MAX_CACHED_IMAGES - MIN_CACHE; // maximum images to cache
2007-06-25 12:57:39 +04:00
volatile ImageFile *list[MAX_CACHED_IMAGES];
int i, total=0, occupied_slots=0, n=0;
2007-06-30 04:25:15 +04:00
// count number of images in use, and size they're using
2007-06-25 12:57:39 +04:00
for (i=0; i < MAX_CACHED_IMAGES; ++i) {
volatile ImageFile *z = &cache[i];
if (z->status != LOAD_unused)
++occupied_slots;
if (MAIN_OWNS(z)) {
if (z->status == LOAD_available) {
total += z->image->stride * z->image->y;
} else if (z->status == LOAD_reading_done) {
total += z->len;
}
list[n++] = z;
2007-06-30 04:25:15 +04:00
} // if main doesn't own, don't worry about it... so we may underestimate sometimes
2007-06-25 12:57:39 +04:00
}
2007-06-30 04:25:15 +04:00
if (!(total > max_cache_bytes || occupied_slots > limit))
return;
// sort by lru
qsort((void *) list, n, sizeof(*list), ImageFilePtrCompare);
// now we free earliest slots on the list...
// we could just leave the cache locked the whole time, but will be slightly smarter
if (!locked) stb_mutex_begin(cache_mutex);
for (i=0; i < n && occupied_slots > MIN_CACHE && (occupied_slots > limit || total > max_cache_bytes); ++i) {
if (MAIN_OWNS(list[i]) && list[i]->status != LOAD_unused) {
// copy the rest of the data out for later use, then clear the existing data
ImageFile p = *list[i];
list[i]->bail = 1; // force disk to bail if it gets this -- can't happen?
list[i]->filename = NULL;
list[i]->filedata = NULL;
list[i]->len = 0;
list[i]->image = NULL;
list[i]->error = NULL;
list[i]->status = LOAD_unused;
// we're done touching this entry (but not done with the data),
// so release the mutex
if (!locked) stb_mutex_end(cache_mutex);
// now do the potentially slow stuff
o(("MAIN: freeing cache: %s\n", p.filename));
stb_sdict_remove(file_cache, p.filename, NULL);
--occupied_slots; // occupied slots
if (p.status == LOAD_available)
total -= p.image->stride * p.image->y;
else if (p.status == LOAD_reading_done)
total -= p.len;
free(p.filename);
if (p.filedata) free(p.filedata);
if (p.image) imfree(p.image);
if (p.error) free(p.error);
// and now we're ready to return to the loop, so reclaim the mutex
if (!locked) stb_mutex_begin(cache_mutex);
2007-06-25 12:57:39 +04:00
}
}
2007-06-30 04:25:15 +04:00
if (!locked) stb_mutex_end(cache_mutex);
o(("Reduced to %d megabytes\n", total >> 20));
2007-06-25 07:45:23 +04:00
}
2007-06-30 04:25:15 +04:00
// keep an index within the 'fileinfo' array
2007-06-25 07:45:23 +04:00
int wrap(int z)
{
2007-06-30 04:25:15 +04:00
int n = stb_arr_len(fileinfo);
2007-06-25 07:45:23 +04:00
if (z < 0) return z + n;
while (z >= n) z = z - n;
return z;
}
2007-06-30 04:25:15 +04:00
// consider adding a file-load command to the disk-load command
// if make_current is true, if it's already loaded, make it current
// (maybe that should be done in advance() instead?)
2007-06-25 07:45:23 +04:00
void queue_disk_command(DiskCommand *dc, int which, int make_current)
{
2007-06-30 04:25:15 +04:00
char *filename;
2007-06-25 07:45:23 +04:00
volatile ImageFile *z;
// check if we already have it cached
filename = fileinfo[which].filename;
z = stb_sdict_get(file_cache, filename);
if (z) {
// we already have a cache slot for this entry.
z->lru = fileinfo[which].lru;
if (!MAIN_OWNS(z)) {
// it's being loaded/decoded
return;
}
2007-06-30 04:25:15 +04:00
// it's waiting to be decoded, so doesn't need queueing
2007-06-29 14:18:49 +04:00
if (z->status == LOAD_reading_done)
return;
2007-06-30 04:25:15 +04:00
// it's already loaded
2007-06-25 07:45:23 +04:00
if (z->status == LOAD_available) {
2007-06-30 04:25:15 +04:00
if (make_current) {
o(("Hey look, make_currentdisk request for %s and it's ready to show!\n", z->filename));
2007-06-25 07:45:23 +04:00
update_source((ImageFile *) z);
2007-06-30 04:25:15 +04:00
}
2007-06-25 07:45:23 +04:00
return;
}
2007-06-30 04:25:15 +04:00
// if it's not inactive and none of the above, it's an error
2007-06-25 07:45:23 +04:00
if (z->status != LOAD_inactive) {
2007-06-27 01:22:01 +04:00
if (make_current) {
set_error(z);
}
2007-06-25 07:45:23 +04:00
return;
}
2007-06-30 04:25:15 +04:00
// z->status == LOAD_inactive
// "fall through" to after the if, below
2007-06-25 07:45:23 +04:00
} else {
2007-06-25 12:57:39 +04:00
int i,tried_again=FALSE;
2007-06-30 04:25:15 +04:00
// didn't already have a cache slot, so find one; we called
// flush_cache() before calling this so a slot should be free
2007-06-25 07:45:23 +04:00
for (i=0; i < MAX_CACHED_IMAGES; ++i)
if (cache[i].status == LOAD_unused)
break;
if (i == MAX_CACHED_IMAGES) {
2007-06-26 05:00:05 +04:00
stb_fatal("Internal logic error: no free cache slots, but flush_cache() should free a few");
return;
2007-06-25 07:45:23 +04:00
}
2007-06-30 04:25:15 +04:00
// allocate this slot and fill in the info
2007-06-25 07:45:23 +04:00
z = &cache[i];
free(z->filename);
2007-06-26 05:00:05 +04:00
assert(z->filedata == NULL);
2007-06-25 07:45:23 +04:00
z->filename = strdup(filename);
z->lru = 0;
z->status = LOAD_inactive;
stb_sdict_add(file_cache, filename, (void *) z);
}
2007-06-30 04:25:15 +04:00
// now, take the z we already had, or just allocated, prep it for loading
2007-06-25 07:45:23 +04:00
assert(z->status == LOAD_inactive);
2007-06-30 04:25:15 +04:00
o(("MAIN: proposing %s\n", z->filename));
z->status = LOAD_inactive; // we still own it for now
2007-06-25 07:45:23 +04:00
z->image = NULL;
z->bail = 0;
2007-06-30 04:25:15 +04:00
z->lru = fileinfo[which].lru; // pass lru value through
2007-06-25 07:45:23 +04:00
2007-06-30 04:25:15 +04:00
// and now really put it on the command list
2007-06-25 07:45:23 +04:00
dc->files[dc->num_files++] = (ImageFile *) z;
}
2007-06-30 04:25:15 +04:00
// step through the current file list
2007-06-25 07:45:23 +04:00
void advance(int dir)
{
DiskCommand dc;
int i;
2007-06-30 04:25:15 +04:00
if (fileinfo == NULL)
2007-06-25 07:45:23 +04:00
init_filelist();
cur_loc = wrap(cur_loc + dir);
// set adjacent files to previous lru value, so they're 2nd-highest priority
fileinfo[wrap(cur_loc-1)].lru = lru_stamp;
fileinfo[wrap(cur_loc+1)].lru = lru_stamp;
// set this file to new value
fileinfo[cur_loc].lru = ++lru_stamp;
2007-06-30 23:21:21 +04:00
// make sure there's room for new images
2007-06-30 04:25:15 +04:00
flush_cache(FALSE);
2007-06-30 23:21:21 +04:00
2007-06-30 04:25:15 +04:00
// we're mucking with the cache like mad, so grab the mutex; it doubles
2007-06-30 23:21:21 +04:00
// as a mutex on dc_shared, so don't release until we're done with dc_shared
stb_mutex_begin(cache_mutex);
2007-06-30 23:21:21 +04:00
{
dc.num_files = 0;
queue_disk_command(&dc, cur_loc, 1); // first thing to load: this file
if (dir) {
queue_disk_command(&dc, wrap(cur_loc+dir), 0); // second thing to load: the next file (preload)
queue_disk_command(&dc, wrap(cur_loc-dir), 0); // last thing to load: the previous file (in case it got skipped when they went fast)
}
filename = fileinfo[cur_loc].filename;
if (dc.num_files) {
dc_shared = dc;
for (i=0; i < dc.num_files; ++i)
assert(dc.files[i]->filedata == NULL);
// wake up the disk thread if needed
stb_sem_release(disk_command_queue);
}
2007-06-25 07:45:23 +04:00
}
stb_mutex_end(cache_mutex);
2007-06-30 23:21:21 +04:00
2007-06-30 04:25:15 +04:00
// tell disk loader not to bother with older files
2007-06-25 07:45:23 +04:00
for (i=0; i < MAX_CACHED_IMAGES; ++i)
if (cache[i].lru < lru_stamp-1)
cache[i].bail = 1;
2007-07-17 13:10:18 +04:00
if (do_show)
SetTimer(win, 0, delay_time, NULL);
2007-06-25 07:45:23 +04:00
}
2007-06-30 04:25:15 +04:00
// ctrl-O, or initial command if no filename: run
// GetOpenFileName(), set as the active filename,
// load the specified filelist, set cur_loc into
// the filelist, and force it to load (and prefetch)
// with 'advance'
static char filenamebuffer[4096];
2007-07-11 21:09:01 +04:00
void open_file(void)
{
OPENFILENAME o = { sizeof(o) };
2007-07-11 21:09:01 +04:00
o.lpstrFilter = open_filter;
o.lpstrFile = filenamebuffer;
filenamebuffer[0] = 0;
o.nMaxFile = sizeof(filenamebuffer);
if (!GetOpenFileName(&o))
return;
filename = filenamebuffer;
stb_fixpath(filename);
stb_splitpath(path_to_file, filename, STB_PATH);
2007-06-26 05:00:05 +04:00
free_fileinfo();
init_filelist();
advance(0);
}
// cleaner casting in C--remember, C macros of the form "foo()" don't
// conflict with uses for 'foo' without a following open parenthesis,
// so this doesn't cause problems
2007-06-25 07:45:23 +04:00
#define int(x) ((int) (x))
// discrete resize operation (from keyboard or mousewheel):
// we want to resize in nice steps, but finer grained than 2x at a time.
// so we resize at sqrt(2) at a time. to prevent rounding errors (so that
// when you size up and back down to 1:1, so it's really 1:1), we actually
// compute a 'zoom factor' that's log2, so zoom=0 is unzoomed, zoom=1 is
// zoomed 2x in each dimension.
2007-06-25 07:45:23 +04:00
void resize(int step)
{
int x = source->x, y = source->y;
2007-06-25 07:45:23 +04:00
float s;
int x2,y2;
int zoom=0; // this is log2(zoom_factor)*2; that is, a 0=1x, 2=2x, 4=4x, 6=8x
2007-06-27 01:22:01 +04:00
// if we have a current image, use that
2007-06-27 01:22:01 +04:00
if (cur) {
// first characterize the current size relative to the raw size
// we do this by linearly probing possible values for zoom
// @TODO: refactor to combine these loops
2007-07-17 13:10:18 +04:00
if (cur->x > x + FRAME*2 || cur->y > y + FRAME*2) {
2007-06-27 01:22:01 +04:00
for(;;) {
s = (float) pow(2, zoom/2.0f + 0.25f);
x2 = int(x*s);
y2 = int(y*s);
if (cur->x < x2 + FRAME*2 || cur->y < y2 + FRAME*2)
break;
++zoom;
}
} else {
for(;;) {
s = (float) pow(2, zoom/2.0f - 0.25f);
x2 = int(x*s);
y2 = int(y*s);
if (cur->x > x2 + FRAME*2 || cur->y > y2 + FRAME*2)
break;
--zoom;
}
2007-06-25 07:45:23 +04:00
}
2007-06-27 01:22:01 +04:00
// now resize
do {
zoom += step;
s = (float) pow(2, zoom/2.0);
if (x*s < 4 || y*s < 4 || x*s > 4000 || y*s > 3000)
return;
x2 = int(x*s) + 2*FRAME;
y2 = int(y*s) + 2*FRAME;
} while (x2 == cur->x || y2 == cur->y);
2007-06-25 07:45:23 +04:00
} else {
// if no current image (e.g. an error), just resize relative to current in power-of-two steps
2007-06-27 01:22:01 +04:00
RECT rect;
2007-06-27 23:42:35 +04:00
GetAdjustedWindowRect(win, &rect);
2007-06-27 01:22:01 +04:00
x2 = rect.right - rect.left;
y2 = rect.bottom - rect.top;
if (step > 0 && x2 <= 1200 && y2 <= 1024)
x2 <<= 1, y2 <<= 1;
if (step < 0 && x2 >= 64 && y2 >= 64)
x2 >>= 1, y2 >>= 1;
2007-06-25 07:45:23 +04:00
}
{
// compute top left to keep same center
2007-06-25 07:45:23 +04:00
RECT rect;
2007-06-27 23:42:35 +04:00
GetAdjustedWindowRect(win, &rect);
2007-06-25 07:45:23 +04:00
x = (rect.left + rect.right)>>1;
y = (rect.top + rect.bottom)>>1;
x -= x2>>1;
y -= y2>>1;
enqueue_resize(x,y,x2,y2);
}
2007-06-26 05:00:05 +04:00
2007-06-27 01:22:01 +04:00
display_mode = zoom==0 ? DISPLAY_actual : DISPLAY_current;
2007-06-25 07:45:23 +04:00
}
// when mouse button is down, what mode are we in?
2007-06-25 07:45:23 +04:00
enum
{
MODE_none,
MODE_drag,
MODE_resize,
} dragmode;
#define setmode(x) (dragmode = (x))
#define ismode(x) (dragmode == (x))
2007-06-25 07:45:23 +04:00
#define anymode() !ismode(MODE_none)
2007-06-30 23:21:21 +04:00
static int ex,ey; // mousedown location relative to top left
static int ex2,ey2; // mousedown location relative to bottom right
static int rx,ry; // sides that are being resized
2007-06-25 07:45:23 +04:00
// compute the borders to the resize-vs-move regions (e.g. for mouse cursor)
2007-06-25 07:45:23 +04:00
static void cursor_regions(int *x0, int *y0, int *x1, int *y1)
{
RECT rect;
int w,h,z2,z;
GetClientRect(win, &rect);
// client dimensions
assert(rect.left == 0 && rect.top == 0);
w = rect.right;
h = rect.bottom;
// size of resize regions is identical in both axes, so
// use smaller window size
z = stb_min(w,h);
2007-06-25 07:45:23 +04:00
// compute size of handles
// this is a pretty ad-hoc logic that is designed to:
// - make them bigger with bigger windows, so easier to grab
// - but not too big
// - make them smaller with smaller windows, so there's still an ample 'move' region
// - but not too small
if (z < 16) z2 = z >> 1;
else if (z < 200) z2 = z >> 2;
else if (z < 800) z2 = z >> 3;
else if (z < 1600) z2 = z >> 4;
else z2 = 100;
*x0 = z2;
*x1 = w - z2;
*y0 = z2;
*y1 = h - z2;
2007-06-25 07:45:23 +04:00
}
// static cursor cache of standard windows cursor for resizing
2007-06-25 07:45:23 +04:00
HCURSOR c_def, c_ne_sw, c_e_w, c_nw_se, c_n_s;
// given the cursor position in client coordinates, set the cursor shape
2007-06-25 07:45:23 +04:00
void set_cursor(int x, int y)
{
int x0,y0,x1,y1;
cursor_regions(&x0,&y0,&x1,&y1);
if (x < x0 && y < y0) SetCursor(c_nw_se);
else if (x > x1 && y > y1) SetCursor(c_nw_se);
else if (x > x1 && y < y0) SetCursor(c_ne_sw);
else if (x < x0 && y > y1) SetCursor(c_ne_sw);
else if (x < x0 || x > x1) SetCursor(c_e_w);
else if (y < y0 || y > y1) SetCursor(c_n_s);
else SetCursor(c_def);
}
// all windows mouse messages that involve the cursor position route here
// (i.e. all mouse messages except mousewheel)
2007-06-25 07:45:23 +04:00
void mouse(UINT ev, int x, int y)
{
switch (ev) {
2007-06-26 05:00:05 +04:00
case WM_LBUTTONDBLCLK:
toggle_display();
break;
2007-06-25 07:45:23 +04:00
case WM_LBUTTONDOWN:
// if we're not in drag/size (and how could we be?!?)
2007-06-25 07:45:23 +04:00
if (!anymode()) {
RECT rect;
int x0,y0,x1,y1;
// determine which region the user clicked in; there are 9 'quadrants',
// three in each dimension, so we measure each dimension separately
2007-06-25 07:45:23 +04:00
cursor_regions(&x0,&y0,&x1,&y1);
rx = (x < x0) ? -1 : (x > x1);
ry = (y < y0) ? -1 : (y > y1);
// if the middle region it's a drag; any other region is a resize
2007-06-25 07:45:23 +04:00
if (rx || ry)
setmode(MODE_resize);
else
setmode(MODE_drag);
// capture the mouse until they let go
2007-06-25 07:45:23 +04:00
SetCapture(win);
// record the position of the mouse cursor relative to the window
// sides, so we can resize properly
2007-06-27 23:42:35 +04:00
GetAdjustedWindowRect(win, &rect);
2007-06-25 07:45:23 +04:00
ex = x;
ey = y;
ex2 = x - (rect.right-rect.left);
ey2 = y - (rect.bottom-rect.top);
}
break;
2007-06-25 07:45:23 +04:00
case WM_MOUSEMOVE:
switch(dragmode) {
default: assert(0);
case MODE_none:
break;
// in drag mode, a mouse move just moves the window
2007-06-25 07:45:23 +04:00
case MODE_drag: {
RECT rect;
GetWindowRect(win, &rect);
MoveWindow(win, rect.left + x-ex, rect.top + y-ey, rect.right - rect.left, rect.bottom - rect.top, TRUE);
break;
}
2007-06-25 07:45:23 +04:00
case MODE_resize: {
RECT rect;
assert(rx || ry);
2007-06-26 05:00:05 +04:00
GetAdjustedWindowRect(win, &rect);
display_mode = DISPLAY_current; // resizing the window forces it out of 'actual' mode
// "LIMIT" controls how small a window can be in each dimension
2007-06-25 07:45:23 +04:00
#define LIMIT 16
// for each direction we're resizing, compute the new position of that edge
if (rx < 0) rect.left = stb_min(rect.left+x-ex , rect.right - LIMIT);
if (rx > 0) rect.right = stb_max(rect.left+x-ex2, rect.left + LIMIT);
if (ry < 0) rect.top = stb_min(rect.top +y-ey , rect.bottom - LIMIT);
if (ry > 0) rect.bottom = stb_max(rect.top +y-ey2, rect.top + LIMIT);
// then force the window to resize to the new rect
2007-06-25 07:45:23 +04:00
enqueue_resize(rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top);
break;
}
}
break;
2007-06-25 07:45:23 +04:00
case WM_RBUTTONUP:
// right mouse click when not modal exits
2007-06-25 07:45:23 +04:00
if (!anymode())
exit(0);
2007-06-25 07:45:23 +04:00
// otherwise, disrupt a modal operation
2007-06-25 07:45:23 +04:00
/* FALLTHROUGH */
2007-06-25 07:45:23 +04:00
case WM_LBUTTONUP:
ReleaseCapture();
setmode(MODE_none);
set_cursor(x,y); // return cursor to normal setting
2007-06-25 07:45:23 +04:00
break;
}
}
static unsigned int physmem; // available physical memory according to GlobalMemoryStatus
2007-06-30 23:21:21 +04:00
char *reg_root = "Software\\SilverSpaceship\\imv";
2007-07-10 13:48:36 +04:00
HKEY zreg;
2007-06-30 23:21:21 +04:00
int reg_get(char *str, void *data, int len)
{
2007-07-10 13:48:36 +04:00
unsigned int type;
if (ERROR_SUCCESS == RegQueryValueEx(zreg, str, 0, &type, data, &len))
if (type == REG_BINARY)
return TRUE;
return FALSE;
2007-06-30 23:21:21 +04:00
}
int reg_set(char *str, void *data, int len)
{
2007-07-10 13:48:36 +04:00
return (ERROR_SUCCESS == RegSetValueEx(zreg, str, 0, REG_BINARY, data, len));
2007-06-30 23:21:21 +04:00
}
2007-07-11 21:09:01 +04:00
int only_stbi=FALSE;
// we use very short strings for these to avoid wasting space, since
// people shouldn't be mucking with them directly anyway!
2007-06-30 23:21:21 +04:00
void reg_save(void)
{
2007-07-10 13:48:36 +04:00
if (ERROR_SUCCESS == RegCreateKeyEx(HKEY_LOCAL_MACHINE, reg_root, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &zreg, NULL))
{
int temp = max_cache_bytes >> 20;
reg_set("ac", &alpha_background, 6);
reg_set("up", &upsample_cubic, 4);
reg_set("cache", &temp, 4);
reg_set("lfs", &label_font_height, 4);
reg_set("label", &show_label, 4);
2007-07-11 21:09:01 +04:00
reg_set("stbi", &only_stbi, 4);
2007-07-10 13:48:36 +04:00
RegCloseKey(zreg);
}
2007-06-30 23:21:21 +04:00
}
void reg_load(void)
{
int temp;
2007-07-10 13:48:36 +04:00
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, reg_root, 0, KEY_READ, &zreg))
{
reg_get("ac", &alpha_background, 6);
reg_get("up", &upsample_cubic, 4);
reg_get("lfs", &label_font_height, 4);
reg_get("label", &show_label, 4);
if (reg_get("cache", &temp, 4))
max_cache_bytes = temp << 20;
2007-07-11 21:09:01 +04:00
reg_get("stbi", &only_stbi, 4);
2007-07-10 13:48:36 +04:00
RegCloseKey(zreg);
}
2007-06-30 23:21:21 +04:00
}
static HWND dialog; // preferences dialog
2007-06-30 23:21:21 +04:00
// set an edit control's text from an integer
2007-06-30 23:21:21 +04:00
static void set_dialog_number(int id, int value)
{
char buffer[16];
sprintf(buffer, "%d", value);
SetWindowText(GetDlgItem(dialog, id), buffer);
}
// get an edit control's text as an integer
2007-06-30 23:21:21 +04:00
static int get_dialog_number(int id)
{
char buffer[32];
int n = GetWindowText(GetDlgItem(dialog,id), buffer, sizeof(buffer)-1);
buffer[n] = 0;
return atoi(buffer);
}
// clamp an edit control's text into an integer range (and
// remove non-integer chacters)
2007-06-30 23:21:21 +04:00
static void dialog_clamp(int id, int low, int high)
{
int x = get_dialog_number(id);
if (x < low) x = low;
else if (x > high) x = high;
else return;
set_dialog_number(id,x);
}
extern unsigned char *rom_images[]; // preference images
// preferences dialog windows procedure
2007-06-30 23:21:21 +04:00
BOOL CALLBACK PrefDlgProc(HWND hdlg, UINT imsg, WPARAM wparam, LPARAM lparam)
{
static Image *pref_image;
int i;
dialog = hdlg; // store dialog handle to not pass it to above functions
2007-06-30 23:21:21 +04:00
switch(imsg)
{
case WM_INITDIALOG: {
// pick a random preference image
2007-06-30 23:21:21 +04:00
int n = ((rand() >> 6) % 3);
int x,y,i,j,k;
// decode it
uint8 *data = stbi_load_from_memory(rom_images[n],2000,&x,&y,NULL,1);
pref_image = bmp_alloc(x,y);
if (data && pref_image) {
// convert monochrome to BGR
2007-06-30 23:21:21 +04:00
for (j=0; j < y; ++j)
for (i=0; i < x; ++i)
for (k=0; k < 3; ++k)
pref_image->pixels[pref_image->stride*j + BPP*i + k] = data[j*x+i];
}
if (data) free(data);
2007-06-30 23:21:21 +04:00
// copy preferences into dialog
SendMessage(GetDlgItem(hdlg, DIALOG_upsample), BM_SETCHECK, upsample_cubic, 0);
SendMessage(GetDlgItem(hdlg, DIALOG_showlabel), BM_SETCHECK, show_label, 0);
2007-07-11 21:09:01 +04:00
SendMessage(GetDlgItem(hdlg, DIALOG_stbi_only), BM_SETCHECK, only_stbi, 0);
2007-06-30 23:21:21 +04:00
for (i=0; i < 6; ++i)
set_dialog_number(DIALOG_r1+i, alpha_background[0][i]);
set_dialog_number(DIALOG_cachesize, max_cache_bytes >> 20);
set_dialog_number(DIALOG_labelheight, label_font_height);
2007-06-30 23:21:21 +04:00
return TRUE;
}
case WM_PAINT: {
if (pref_image) {
// draw the preferences image
RECT z;
int x,y;
HWND pic = GetDlgItem(hdlg, DIALOG_image);
GetWindowRect(pic,&z);
// not clear why these next two lines work/are required, but it's what petzold does;
// doesn't UpdateWindow() just force a WM_PAINT? why isn't this an infinite loop?
InvalidateRect(pic,NULL,TRUE);
UpdateWindow(pic);
// center it
x = (z.right - z.left - pref_image->x) >> 1;
y = (z.bottom - z.top - pref_image->y) >> 1;
platformDrawBitmap(GetDC(pic), x,y,pref_image->pixels,pref_image->x,pref_image->y,pref_image->stride,0);
}
2007-06-30 23:21:21 +04:00
break;
}
case WM_COMMAND: {
int k = LOWORD(wparam);
int n = HIWORD(wparam);
switch(k) {
// validate the dialog entries into range and numeric only, when
// they lose focus only
2007-06-30 23:21:21 +04:00
case DIALOG_r1: case DIALOG_g1: case DIALOG_b1:
case DIALOG_r2: case DIALOG_g2: case DIALOG_b2:
if (n == EN_KILLFOCUS) dialog_clamp(k,0,255);
break;
case DIALOG_cachesize:
if (n == EN_KILLFOCUS) dialog_clamp(k,1,(physmem>>22)*3); // 3/4 of phys mem
break;
case DIALOG_labelheight:
if (n == EN_KILLFOCUS) dialog_clamp(k,1,200);
break;
2007-06-30 23:21:21 +04:00
case IDOK: {
// user clicked ok... copy out current values to check for changes
2007-06-30 23:21:21 +04:00
unsigned char cur[6];
int old_cubic = upsample_cubic;
2007-06-30 23:21:21 +04:00
memcpy(cur, alpha_background, 6);
2007-06-30 23:21:21 +04:00
// load the settings back out of the dialog box
for (i=0; i < 6; ++i)
alpha_background[0][i] = get_dialog_number(DIALOG_r1+i);
max_cache_bytes = get_dialog_number(DIALOG_cachesize) << 20;
label_font_height = get_dialog_number(DIALOG_labelheight);
upsample_cubic = BST_CHECKED == SendMessage(GetDlgItem(hdlg,DIALOG_upsample ), BM_GETCHECK,0,0);
show_label = BST_CHECKED == SendMessage(GetDlgItem(hdlg,DIALOG_showlabel), BM_GETCHECK,0,0);
2007-07-11 21:09:01 +04:00
only_stbi = BST_CHECKED == SendMessage(GetDlgItem(hdlg,DIALOG_stbi_only), BM_GETCHECK,0,0);
// if alpha_background changed, clear the cache of any images that used it
2007-06-30 23:21:21 +04:00
if (memcmp(alpha_background, cur, 6)) {
stb_mutex_begin(cache_mutex);
for (i=0; i < MAX_CACHED_IMAGES; ++i) {
if (cache[i].status == LOAD_available) {
if (cache[i].image->had_alpha) {
stb_sdict_remove(file_cache, cache[i].filename, NULL);
free(cache[i].filename);
imfree(cache[i].image);
cache[i].status = LOAD_unused;
cache[i].image = NULL;
cache[i].filename = NULL;
}
}
}
stb_mutex_end(cache_mutex);
// force a reload of the current image
2007-06-30 23:21:21 +04:00
advance(0);
2007-07-15 14:59:15 +04:00
} else if (old_cubic != upsample_cubic) {
free(cur_filename);
cur_filename = NULL;
advance(0);
2007-07-15 14:59:15 +04:00
}
// save the data out to the registry
2007-06-30 23:21:21 +04:00
reg_save();
// rebuild the label font
build_label_font();
// redraw window -- only needed if changed label state, but we can live with always
InvalidateRect(win, NULL, FALSE);
2007-06-30 23:21:21 +04:00
/* FALL THROUGH */
}
case IDCANCEL:
EndDialog(hdlg,0);
return TRUE;
}
break;
}
case WM_DESTROY:
// we're closing the dialog, so clear the cached image
imfree(pref_image);
pref_image = NULL;
break;
2007-06-30 23:21:21 +04:00
}
return FALSE;
}
2007-07-11 21:09:01 +04:00
#ifdef PERFTEST
void performance_test(void)
{
int t1,t2;
int len,i;
uint8 *buffer = stb_file(cur_filename, &len);
if (buffer == NULL) return;
t1 = timeGetTime();
for (i=0; i < 50; ++i) {
int x,y,n;
uint8 *result = imv_decode_from_memory(buffer, len, &x, &y, &n, 4);
free(result);
}
t2 = timeGetTime();
free(buffer);
{
char buffer[512];
sprintf(buffer, "Decode time: %f ms\n", (t2-t1)/50.0);
error(buffer);
}
}
#endif
// missing VK definitions in old compiler
2007-06-25 07:45:23 +04:00
#ifndef VK_OEM_PLUS
#define VK_OEM_PLUS 0xbb
#define VK_OEM_MINUS 0xbd
#endif
2007-06-26 08:17:12 +04:00
#ifndef VK_SLASH
#define VK_SLASH 0xbf
#endif
2007-06-25 07:45:23 +04:00
// ok, surely windows doesn't BY DESIGN require you to store your
// HINSTANCE in a global, does it? but I couldn't find a 'GetCurrentInstance'
// or some such to tell you what instance a thread came from. But the
// HINSTANCE is needed to launch the preferences dialog. Oh well!
2007-06-30 23:21:21 +04:00
HINSTANCE inst;
2007-06-29 02:57:49 +04:00
2007-06-25 07:45:23 +04:00
int WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_CREATE: {
win = hWnd;
break;
}
2007-06-27 01:22:01 +04:00
case WM_APP_LOAD_ERROR:
case WM_APP_DECODE_ERROR:
{
// if the load/decode threads get an error, they send this message
// to make sure the main thread gets woken up. then we have to decide
// whether to show it or not; we do that by scanning the whole cache
// to see what the most recently-browsed-and-displayable image is,
// and store that in 'best'.
2007-06-27 01:22:01 +04:00
int best_lru=0,i;
volatile ImageFile *best = NULL;
for (i=0; i < MAX_CACHED_IMAGES; ++i) {
if (cache[i].lru > best_lru) {
if (MAIN_OWNS(&cache[i])) {
if (cache[i].status >= LOAD_error_reading) {
best_lru = cache[i].lru;
best = &cache[i];
}
}
}
}
// if the most recently-browsed and displayable image is an error, show it
if (best->status == LOAD_error_reading || best->status == LOAD_error_decoding)
2007-06-27 01:22:01 +04:00
set_error(best);
break;
}
2007-06-25 07:45:23 +04:00
case WM_APP_DECODED: {
// if the decode thread finishes, it sends us this message. note that
// we skip files that had an error; but we use a global variable for 'best_lru'
// so we won't ever retreat. I'm not sure how this really interacts with
// the above loop, though. maybe they should be combined.
2007-06-25 07:45:23 +04:00
int i;
ImageFile *best = NULL;
for (i=0; i < stb_arr_len(fileinfo); ++i) {
if (fileinfo[i].lru > best_lru) {
ImageFile *z = stb_sdict_get(file_cache, fileinfo[i].filename);
if (z && z->status == LOAD_available) {
assert(z->image != NULL);
best = z;
best_lru = fileinfo[i].lru;
}
}
}
if (best) {
2007-06-30 04:25:15 +04:00
o(("Post-decode, found a best image, better than any before.\n"));
2007-06-25 07:45:23 +04:00
update_source(best);
}
// since we've decoded a new image, our cache might be too big,
// so try flushing it
2007-06-25 12:57:39 +04:00
flush_cache(FALSE);
2007-06-25 07:45:23 +04:00
break;
}
2007-06-27 01:22:01 +04:00
case WM_MOUSEWHEEL: {
int zdelta = (short) HIWORD(wParam);
// ignore wheel scaling factor and step 1 by 1
2007-06-27 01:22:01 +04:00
if (zdelta > 0) resize(1);
if (zdelta < 0) resize(-1);
break;
}
2007-06-25 07:45:23 +04:00
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_LBUTTONUP:
case WM_RBUTTONUP:
2007-06-26 05:00:05 +04:00
case WM_LBUTTONDBLCLK:
2007-06-25 07:45:23 +04:00
mouse(uMsg, (short) LOWORD(lParam), (short) HIWORD(lParam));
return 0;
case WM_SETCURSOR: {
// there's no GetCursorPos for the client window?
2007-06-25 07:45:23 +04:00
POINT p;
if (GetCursorPos(&p)) {
RECT rect;
GetWindowRect(win, &rect);
set_cursor(p.x - rect.left, p.y - rect.top);
return TRUE;
}
return FALSE;
}
case WM_PAINT: {
PAINTSTRUCT ps;
2007-06-29 02:57:49 +04:00
HDC hDC = BeginPaint(hWnd, &ps);
2007-06-25 07:45:23 +04:00
display(hWnd, hDC);
EndPaint(hWnd, &ps);
return 0;
}
2007-07-17 13:10:18 +04:00
case WM_TIMER: {
advance(1);
return 0;
break;
}
2007-06-25 07:45:23 +04:00
#define MY_SHIFT (1 << 16)
#define MY_CTRL (1 << 17)
#define MY_ALT (1 << 18)
case WM_CHAR: {
int code = (GetKeyState(VK_SHIFT) < 0 ? MY_SHIFT : 0)
| (GetKeyState(VK_CONTROL) < 0 ? MY_CTRL : 0);
2007-06-25 07:45:23 +04:00
code += wParam;
switch (wParam) {
2007-07-10 13:48:36 +04:00
2007-06-25 07:45:23 +04:00
case 27:
2007-07-10 13:48:36 +04:00
if (!show_help)
exit(0);
show_help = !show_help;
InvalidateRect(win, NULL, FALSE);
break;
2007-06-25 07:45:23 +04:00
case ' ': // space
advance(1);
break;
case 0x08: // backspace
advance(-1);
break;
2007-06-27 23:42:35 +04:00
case 'l': case 'L':
show_label = !show_label;
InvalidateRect(win, NULL, FALSE);
break;
2007-07-17 13:10:18 +04:00
case 'S':
do_show = !do_show;
if (do_show)
SetTimer(win,0,delay_time,NULL);
else
KillTimer(win,0);
break;
2007-06-25 07:45:23 +04:00
default:
return 1;
}
return 0;
}
case WM_KEYDOWN:
case WM_SYSKEYDOWN: {
int code =(GetKeyState(VK_SHIFT) < 0 ? MY_SHIFT : 0)
| (GetKeyState(VK_CONTROL) < 0 ? MY_CTRL : 0)
| (GetKeyState(VK_MENU ) < 0 ? MY_ALT : 0);
code += wParam;
switch (code) {
case VK_RIGHT:
case VK_NUMPAD6:
advance(1);
break;
case VK_LEFT:
case VK_NUMPAD4:
advance(-1);
break;
2007-06-26 08:17:12 +04:00
case VK_F1:
case 'H':
case 'H' | MY_SHIFT:
case VK_SLASH:
case VK_SLASH | MY_SHIFT:
show_help = !show_help;
InvalidateRect(win, NULL, FALSE);
break;
2007-07-17 13:10:18 +04:00
#ifdef MONO_THUMB
case 'T' | MY_CTRL | MY_ALT | MY_SHIFT:
{
extern Image *make_mono_thumb(Image *src);
while (pending_resize.size.w && !pending_resize.image)
Sleep(10);
source = make_mono_thumb(source);
imfree(source_c->image);
source_c->image = source;
size_to_current(FALSE);
break;
}
#endif
case 'B' | MY_CTRL:
2007-06-27 23:42:35 +04:00
extra_border = !extra_border;
if (cur) frame(cur);
InvalidateRect(win, NULL, FALSE);
break;
case 'B' | MY_SHIFT:
2007-06-27 23:42:35 +04:00
toggle_frame();
break;
case 'B':
2007-06-27 23:42:35 +04:00
toggle_frame();
extra_border = show_frame;
if (cur) frame(cur);
break;
2007-07-15 14:47:50 +04:00
case 'C' | MY_CTRL: {
HGLOBAL hMem;
char buffer[1024], *t;
stb_fullpath(buffer, 1024, source_c->filename);
hMem = GlobalAlloc(GHND, strlen(buffer)+1);
t = GlobalLock(hMem);
strcpy(t, buffer);
GlobalUnlock(hMem);
OpenClipboard(win);
EmptyClipboard();
SetClipboardData(CF_TEXT, hMem);
CloseClipboard();
break;
}
2007-07-17 13:10:18 +04:00
case 'I' | MY_CTRL: {
// not sure which of these is smaller
#if 0
char buffer[MAX_PATH+1024];
PROCESS_INFORMATION pi={0};
STARTUPINFO si={0};
buffer[0] = '"';
GetModuleFileName(NULL, buffer+1, MAX_PATH);
strcat(buffer, "\" \"");
stb_fullpath(buffer+strlen(buffer), 1020, source_c->filename);
strcat(filename, "\"");
CreateProcess(NULL, buffer, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &si, &pi);
#else
char buffer[MAX_PATH],filename[1024] = {'\"'};
GetModuleFileName(NULL, buffer, sizeof(buffer));
stb_fullpath(filename+1, sizeof(filename)-2, source_c->filename);
strcat(filename, "\"");
_spawnl(_P_NOWAIT, buffer, buffer, filename, NULL);
#endif
break;
}
2007-07-11 21:09:01 +04:00
#ifdef PERFTEST
case 'D' | MY_CTRL:
performance_test();
break;
#endif
2007-06-27 23:42:35 +04:00
2007-06-30 23:21:21 +04:00
case 'P':
case 'P' | MY_CTRL:
DialogBox(inst, MAKEINTRESOURCE(IDD_pref), hWnd, PrefDlgProc);
break;
2007-06-25 07:45:23 +04:00
case MY_CTRL | VK_OEM_PLUS:
case MY_CTRL | MY_SHIFT | VK_OEM_PLUS:
resize(1);
break;
case MY_CTRL | VK_OEM_MINUS:
resize(-1);
break;
case 'O':
case MY_CTRL | 'O':
open_file();
break;
2007-06-25 07:45:23 +04:00
case MY_ALT | '\r':
2007-06-26 05:00:05 +04:00
toggle_display();
2007-06-25 07:45:23 +04:00
break;
default:
return DefWindowProc (hWnd, uMsg, wParam, lParam);
}
break;
}
case WM_DESTROY:
PostQuitMessage (0);
break;
default:
return DefWindowProc (hWnd, uMsg, wParam, lParam);
}
return 1;
}
// number of threads to use in resizer
2007-06-25 07:45:23 +04:00
int resize_threads;
// whether 'cur' (the resized image currently displayed) actually comes from 'source'
2007-06-26 05:00:05 +04:00
int cur_is_current(void)
{
if (!cur_filename) return FALSE;
if (!source_c || !source_c->filename) return FALSE;
return !strcmp(cur_filename, source_c->filename);
}
2007-06-25 07:45:23 +04:00
2007-07-11 21:09:01 +04:00
#ifdef USE_FREEIMAGE
static int FreeImagePresent;
static int LoadFreeImage(void);
#endif
2007-06-25 07:45:23 +04:00
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
int argc;
char **argv = stb_tokens_quoted(lpCmdLine, " ", &argc);
char filenamebuffer[4096];
2007-06-25 07:45:23 +04:00
MEMORYSTATUS mem;
MSG msg;
WNDCLASSEX wndclass = { sizeof(wndclass) };
HWND hWnd;
2007-06-25 07:45:23 +04:00
// initial loaded image
int image_x, image_y, image_n;
2007-06-25 07:45:23 +04:00
unsigned char *image_data;
2007-06-30 23:21:21 +04:00
inst = hInstance;
2007-07-17 13:10:18 +04:00
#ifdef _DEBUG
{
char buffer[1024];
FILE *f = fopen("c:/x/cmdline.txt", "ab");
sprintf(buffer, "%s\n\nx\n", lpCmdLine);
fwrite(buffer, 1, strlen(buffer), f);
fclose(f);
}
#endif
// determine the number of threads to use in the resizer
resize_threads = stb_min(stb_processor_count(), 16);
2007-06-25 07:45:23 +04:00
// compute the amount of physical memory to set a guess for the cache size
2007-06-25 07:45:23 +04:00
GlobalMemoryStatus(&mem);
2007-06-30 23:21:21 +04:00
if (mem.dwTotalPhys == 0) --mem.dwTotalPhys;
2007-06-25 07:45:23 +04:00
physmem = mem.dwTotalPhys;
2007-06-27 01:22:01 +04:00
max_cache_bytes = physmem / 6;
if (max_cache_bytes > 256 << 20) max_cache_bytes = 256 << 20;
// load the registry preferences, if they're there (AFTER the above)
2007-06-30 23:21:21 +04:00
reg_load();
2007-07-11 21:09:01 +04:00
// concatenate the version number onto the help text, because
// we can't do this statically with the current build process
strcat(helptext_center, VERSION);
// now try to LoadFreeImage _after_ we've already loaded the prefs;
// that way only_stbi can be used to suppress errors
#ifdef USE_FREEIMAGE
if (LoadFreeImage()) {
strcat(helptext_center, "\nUsing FreeImage.dll: http://freeimage.sourceforge.net");
2007-07-11 21:09:01 +04:00
open_filter = "Image Files\0*.jpg;*.jpeg;*.png;*.bmp;*.dds;*.gif;*.ico;*.jng;*.lbm;*.pcx;*.ppm;*.psd;*.tga;*.tiff\0";
}
#endif
assert(helptext_center[sizeof(helptext_center)-1]==0);
// create the main window class
2007-06-26 05:00:05 +04:00
wndclass.style = CS_OWNDC | CS_DBLCLKS;
2007-06-25 07:45:23 +04:00
wndclass.lpfnWndProc = (WNDPROC)MainWndProc;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(hInstance, szAppName);
wndclass.hCursor = LoadCursor(NULL,IDC_ARROW);
wndclass.hbrBackground = NULL;
2007-06-25 07:45:23 +04:00
wndclass.lpszMenuName = szAppName;
wndclass.lpszClassName = szAppName;
wndclass.hIconSm = LoadIcon(hInstance, szAppName);
if (!RegisterClassEx(&wndclass))
return FALSE;
// cache the cursors
c_def = LoadCursor(NULL, IDC_ARROW);
2007-06-25 07:45:23 +04:00
c_nw_se = LoadCursor(NULL, IDC_SIZENWSE);
c_ne_sw = LoadCursor(NULL, IDC_SIZENESW);
c_e_w = LoadCursor(NULL, IDC_SIZEWE);
c_n_s = LoadCursor(NULL, IDC_SIZENS);
build_label_font();
2007-06-27 23:42:35 +04:00
2007-06-30 23:21:21 +04:00
srand(time(NULL));
2007-06-27 23:42:35 +04:00
if (argc < 1) {
// if run with no arguments, get an initial filename
OPENFILENAME o = { sizeof(o) };
2007-07-11 21:09:01 +04:00
o.lpstrFilter = open_filter;
o.lpstrFile = filenamebuffer;
filenamebuffer[0] = 0;
o.nMaxFile = sizeof(filenamebuffer);
if (!GetOpenFileName(&o))
return 0;
filename = filenamebuffer;
} else {
// else grab the first one... what about additional names? should
// we launch more windows? or initialize the filelist to them, I guess?
filename = argv[0];
}
2007-06-25 07:45:23 +04:00
// allocate worker threads
2007-06-25 07:45:23 +04:00
resize_workers = stb_workq_new(resize_threads, resize_threads * 4);
// load initial image
{
char *why=NULL;
int len;
uint8 *data = stb_file(filename, &len);
if (!data)
why = "Couldn't open file";
else {
2007-07-11 21:09:01 +04:00
image_data = imv_decode_from_memory(data, len, &image_x, &image_y, &image_n, BPP);
if (image_data == NULL)
why = imv_failure_reason();
}
if (why) {
// we treat errors on initial image differently: message box and exit...
// now that we handle errors nicely, this is kind of dumb... but what
// size should the initial window be?
char buffer[512];
sprintf(buffer, "'%s': %s", filename, why);
error(buffer);
exit(0);
}
2007-06-25 07:45:23 +04:00
}
// fix the filename & path for consistency with readdir()
stb_fixpath(filename);
// extract just the path
stb_splitpath(path_to_file, filename, STB_PATH);
// allocate semaphores / mutexes
cache_mutex = stb_mutex_new();
decode_mutex = stb_mutex_new();
decode_queue = stb_sem_new(1);
disk_command_queue = stb_sem_new(1);
resize_merge = stb_sync_new();
// go ahead and start the other tasks
stb_create_thread(diskload_task, NULL);
stb_create_thread(decode_task, NULL);
2007-06-25 07:45:23 +04:00
// create the source image by converting the image data to BGR,
// pre-blending alpha
2007-06-25 07:45:23 +04:00
source = malloc(sizeof(*source));
make_image(source, image_x, image_y, image_data, image_n);
// create a cache entry in case they start browsing later
2007-06-25 07:45:23 +04:00
cache[0].status = LOAD_available;
cache[0].image = source;
cache[0].lru = lru_stamp++;
cache[0].filename = strdup(filename);
file_cache = stb_sdict_new(1);
stb_sdict_add(file_cache, filename, (void *) &cache[0]);
source_c = (ImageFile *) &cache[0];
2007-06-25 07:45:23 +04:00
{
int x,y;
int w2 = source->x+FRAME*2, h2 = source->y+FRAME*2;
int w,h;
// determine an initial window size, and resize
2007-06-25 07:45:23 +04:00
ideal_window_size(w2,h2, &w,&h, &x,&y);
// if the size exactly matches, don't resize, just copy
2007-06-25 07:45:23 +04:00
if (w == source->x+FRAME*2 && h == source->y+FRAME*2) {
2007-06-27 01:22:01 +04:00
display_error[0] = 0;
2007-06-25 07:45:23 +04:00
cur = bmp_alloc(image_x + FRAME*2, image_y + FRAME*2);
frame(cur);
{
int j;
unsigned char *p = image_data;
for (j=0; j < image_y; ++j) {
unsigned char *q = cur->pixels + (j+FRAME)*cur->stride + FRAME*BPP;
memcpy(q, p, image_x*BPP);
p += image_x*BPP;
}
}
w=w;
} else {
// size is not an exact match
queue_resize(w,h, (ImageFile *) &cache[0], TRUE);
2007-06-27 01:22:01 +04:00
display_error[0] = 0;
2007-06-25 07:45:23 +04:00
cur = pending_resize.image;
pending_resize.image = NULL;
}
2007-06-26 05:00:05 +04:00
cur_filename = strdup(filename);
2007-06-25 07:45:23 +04:00
// create the window
2007-06-25 07:45:23 +04:00
hWnd = CreateWindow(szAppName, displayName,
WS_POPUP,
x,y, w, h,
NULL, NULL, hInstance, NULL);
if (!hWnd)
return FALSE;
} // open brace for defining some temporary variables
2007-06-25 07:45:23 +04:00
// display the window
2007-06-25 07:45:23 +04:00
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
InvalidateRect(hWnd, NULL, TRUE);
for(;;) {
// if we're not currently resizing, and there's a resize request
2007-06-25 07:45:23 +04:00
if (qs.w && pending_resize.size.w == 0) {
2007-06-29 14:18:49 +04:00
if (source) {
// is the image we're showing the image to resize, and does the size match?
2007-06-29 14:18:49 +04:00
if (cur_is_current() && (!cur || (qs.w == cur->x && qs.h >= cur->y) || (qs.h == cur->y && qs.w >= cur->x))) {
// no resize necessary, just a variant of the current shape
2007-07-17 13:10:18 +04:00
if (!show_frame) qs.x += FRAME, qs.y += FRAME, qs.w -= 2*FRAME, qs.h -= 2*FRAME;
2007-06-29 14:18:49 +04:00
MoveWindow(win, qs.x,qs.y,qs.w,qs.h, TRUE);
InvalidateRect(win, NULL, FALSE);
} else {
o(("Enqueueing resize\n"));
pending_resize.size = qs;
queue_resize(qs.w, qs.h, source_c, FALSE);
}
2007-07-15 14:59:15 +04:00
//flush = FALSE;
2007-06-25 07:45:23 +04:00
}
qs.w = 0;
}
// this is currently done in a stupid way--we should have the resizer
// post us a message to wake us up, but I wrote this before I had
// that infrastructure worked out
2007-06-25 07:45:23 +04:00
if (!PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE)) {
// no messages, so check for a resize completing
2007-06-25 07:45:23 +04:00
if (pending_resize.size.w) {
// there's a resize pending, so don't block
if (!pending_resize.image) {
2007-07-15 14:47:50 +04:00
// @TODO: use a message instead!
// resize isn't done, so sleep for a bit and try again
2007-06-25 07:45:23 +04:00
Sleep(10);
continue;
2007-06-25 07:45:23 +04:00
} else {
// resize is done
2007-06-29 02:57:49 +04:00
HDC hdc;
2007-06-29 14:18:49 +04:00
o(("Finished resize\n"));
// reclaim ownership of the image from the resizer
2007-06-29 14:18:49 +04:00
pending_resize.image_c->status = LOAD_available;
// free the current image we're about to write over
imfree(cur);
2007-06-25 07:45:23 +04:00
cur = pending_resize.image;
// pending_resize.filename was strdup()ed, so just take ownership of it
2007-06-26 05:00:05 +04:00
cur_filename = pending_resize.filename;
pending_resize.filename = NULL;
// clear error messages
display_error[0] = 0;
2007-06-27 23:42:35 +04:00
if (!show_frame) {
pending_resize.size.x += FRAME;
pending_resize.size.y += FRAME;
pending_resize.size.w -= FRAME*2;
pending_resize.size.h -= FRAME*2;
}
// resize the window
2007-07-17 13:10:18 +04:00
SetWindowPos(hWnd,NULL,pending_resize.size.x, pending_resize.size.y, pending_resize.size.w, pending_resize.size.h, SWP_NOZORDER|SWP_NOCOPYBITS);
//MoveWindow(hWnd,pending_resize.size.x, pending_resize.size.y, pending_resize.size.w, pending_resize.size.h, FALSE);
// clear the resize request info
2007-06-25 07:45:23 +04:00
barrier();
pending_resize.size.w = 0;
// paint the window
2007-06-29 02:57:49 +04:00
hdc = GetDC(win);
display(hWnd, hdc);
ReleaseDC(win, hdc);
// restart from the top
continue;
2007-06-25 07:45:23 +04:00
}
}
}
// we can get rid of this with peek-with-remove, surely?
2007-06-25 07:45:23 +04:00
if (!GetMessage(&msg, NULL, 0, 0))
return msg.wParam;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
/////////////////////////////////////////////////////////////////////////////
//
// Everything from here on down just does image resizing
//
2007-06-25 07:45:23 +04:00
typedef struct {
short i;
unsigned char f;
} SplitPoint;
SplitPoint point_buffer[3200];
typedef struct
{
double temp;
Image *dest;
Image *src;
SplitPoint *p;
int j0,j1;
float dy;
} ImageProcess;
#define CACHE_REBLOCK 64
void *image_resize_work(ImageProcess *q)
{
int i,j,k,n=0;
Image *dest = q->dest, *src = q->src;
SplitPoint *p = q->p;
float y, y0 = q->dy * q->j0;
for (k=0; k < dest->x; k += CACHE_REBLOCK) {
int k2 = stb_min(k + CACHE_REBLOCK, dest->x);
y = y0;
for (j=q->j0; j < q->j1; ++j) {
int iy;
int fy;
y = q->dy * j;
iy = (int) floor(y);
fy = (int) floor(255.9f*(y - iy));
if (iy >= src->y-1) {
iy = src->y-2;
fy = 255;
}
{
unsigned char *d = &dest->pixels[j*dest->stride + k*BPP];
unsigned char *s0 = src->pixels + src->stride*iy;
unsigned char *s1 = s0 + src->stride;
for (i=k; i < k2; ++i) {
s0 += p[i].i;
s1 += p[i].i;
{
unsigned char x = p[i].f;
#if BPP == 4
uint32 c00,c01,c10,c11,rb0,rb1,rb00,rb01,rb10,rb11,rb,g;
c00 = *(uint32 *) s0;
c01 = *(uint32 *) (s0+4);
c10 = *(uint32 *) s1;
c11 = *(uint32 *) (s1+4);
rb00 = c00 & 0xff00ff;
rb01 = c01 & 0xff00ff;
rb0 = (rb00 + (((rb01 - rb00) * x) >> 8)) & 0xff00ff;
rb10 = c10 & 0xff00ff;
rb11 = c11 & 0xff00ff;
rb1 = (rb10 + (((rb11 - rb10) * x) >> 8)) & 0xff00ff;
rb = (rb0 + (((rb1 - rb0) * fy) >> 8)) & 0xff00ff;
rb00 = c00 & 0xff00;
rb01 = c01 & 0xff00;
rb0 = (rb00 + (((rb01 - rb00) * x) >> 8)) & 0xff00;
rb10 = c10 & 0xff00;
rb11 = c11 & 0xff00;
rb1 = (rb10 + (((rb11 - rb10) * x) >> 8)) & 0xff00;
g = (rb0 + (((rb1 - rb0) * fy) >> 8)) & 0xff00;
*(uint32 *)d = rb + g;
#else
unsigned char v00,v01,v10,v11;
int v0,v1;
v00 = s0[0]; v01 = s0[BPP+0]; v10 = s1[0]; v11 = s1[BPP+0];
v0 = (v00<<8) + x * (v01 - v00);
v1 = (v10<<8) + x * (v11 - v10);
v0 = (v0<<8) + fy * (v1 - v0);
d[0] = v0 >> 16;
v00 = s0[1]; v01 = s0[BPP+1]; v10 = s1[1]; v11 = s1[BPP+1];
v0 = (v00<<8) + x * (v01 - v00);
v1 = (v10<<8) + x * (v11 - v10);
v0 = (v0<<8) + fy * (v1 - v0);
d[1] = v0 >> 16;
v00 = s0[2]; v01 = s0[BPP+2]; v10 = s1[2]; v11 = s1[BPP+2];
v0 = (v00<<8) + x * (v01 - v00);
v1 = (v10<<8) + x * (v11 - v10);
v0 = (v0<<8) + fy * (v1 - v0);
d[2] = v0 >> 16;
#endif
d += BPP;
}
}
}
y += q->dy;
}
}
return NULL;
}
2007-06-30 04:25:15 +04:00
void image_resize_bilinear(Image *dest, Image *src)
2007-06-29 14:18:49 +04:00
{
ImageProcess proc_buffer[16], *q = stb_temp(proc_buffer, resize_threads * sizeof(*q));
SplitPoint *p = stb_temp(point_buffer, dest->x * sizeof(*p));
int i,j0,j1,k;
float x,dx,dy;
assert(src->frame == 0);
dx = (float) (src->x - 1) / (dest->x - 1);
dy = (float) (src->y - 1) / (dest->y - 1);
x=0;
for (i=0; i < dest->x; ++i) {
p[i].i = (int) floor(x);
p[i].f = (int) floor(255.9f*(x - p[i].i));
if (p[i].i >= src->x-1) {
p[i].i = src->x-2;
p[i].f = 255;
}
x += dx;
p[i].i *= BPP;
}
for (k=0; k < dest->x; k += CACHE_REBLOCK) {
int k2 = stb_min(k+CACHE_REBLOCK, dest->x);
for (i=k2-1; i > k; --i) {
p[i].i -= p[i-1].i;
}
}
j0 = 0;
for (i=0; i < resize_threads; ++i) {
j1 = dest->y * (i+1) / resize_threads;
q[i].dest = dest;
q[i].src = src;
q[i].j0 = j0;
q[i].j1 = j1;
q[i].dy = dy;
q[i].p = p;
j1 = j0;
}
if (resize_threads == 1) {
image_resize_work(q);
} else {
stb_sync_set_target(resize_merge, resize_threads);
2007-06-29 14:18:49 +04:00
for (i=1; i < resize_threads; ++i)
stb_workq_reach(resize_workers, image_resize_work, q+i, NULL, resize_merge);
2007-06-29 14:18:49 +04:00
image_resize_work(q);
stb_sync_reach_and_wait(resize_merge);
2007-06-29 14:18:49 +04:00
}
stb_tempfree(point_buffer, p);
stb_tempfree(proc_buffer , q);
}
2007-06-29 02:57:49 +04:00
2007-06-30 02:00:49 +04:00
#if BPP==4
2007-06-29 02:57:49 +04:00
//
#undef R
#undef G
#undef B
#undef A
#undef RGB
#undef RGBA
#define R(x) ( (x) & 0xff)
#define G(x) (((x) >> 8) & 0xff)
#define B(x) (((x) >> 16) & 0xff)
#define A(x) (((x) >> 24) & 0xff)
#define RGBA(r,g,b,a) (((a) << 24) + ((b) << 16) + ((g) << 8) + (r))
#define RGB(r,g,b) RGBA(r,g,b,0)
typedef uint32 Color;
// lerp() is just blend() that also "blends" alpha
// put a/256 of src over dest, including alpha
// again, cannot be used for a=256
static Color lerp(Color dest, Color src, uint8 a)
{
int rb_src = src & 0xff00ff;
int rb_dest = dest & 0xff00ff;
int rb = rb_dest + ((rb_src - rb_dest) * a >> 8);
int ga_src = (src & 0xff00ff00) >> 8;
int ga_dest = (dest & 0xff00ff00) >> 8;
int ga = (ga_dest<<8) + (ga_src - ga_dest) * a;
return (rb & 0xff00ff) + (ga & 0xff00ff00);
}
2007-06-29 14:18:49 +04:00
#if 1
#define SSE __declspec(align(16))
#define MMX __declspec(align(8))
// out = a * t^3 + b*t^2 + c*t + d
// out = (a*t+b)*t^2 + (c*t+d)*1
MMX int16 three[4] = { 3,3,3,3 };
2007-06-30 04:25:15 +04:00
static void cubic_interpolate_span(uint32 *dest,
uint32 *x0, uint32 *x1, uint32 *x2, uint32 *x3,
int lerp8, int step_dest, int step_src, int len)
2007-06-29 14:18:49 +04:00
{
if (len <= 0) return;
__asm {
2007-06-30 04:25:15 +04:00
// these save/restores shouldn't be necessary... but they seem to be needed
// in VC6 opt builds; either a buggy compiler, or I'm doing something wrong
2007-06-29 14:18:49 +04:00
push eax
push ebx
push ecx
push edx
push esi
push edi
mov edi,dest
mov eax,x0
mov ebx,x1
mov ecx,x2
mov edx,x3
pxor mm0,mm0
movd mm7,lerp8
mov esi,len
punpcklbw mm7,mm7 // 0,0,0,0,0,0,lerp,lerp
punpcklbw mm7,mm7 // 0,0,0,0,lerp,lerp,lerp,lerp
punpcklbw mm7,mm7 // 8xlerp. (This meakes each unsigned lerp value 0..15)
psrlw mm7,1 // slide away from the sign bit; 1.15 lerp
} looptop: __asm {
movd mm1,[eax]
2007-06-30 02:00:49 +04:00
movd mm4,[edx]
2007-06-29 14:18:49 +04:00
movd mm2,[ebx]
movd mm3,[ecx]
2007-06-30 02:00:49 +04:00
add eax,step_src
add ebx,step_src
2007-06-29 14:18:49 +04:00
punpcklbw mm1,mm0 // mm1 = x0
2007-06-30 02:00:49 +04:00
punpcklbw mm4,mm0 // mm4 = x3
2007-06-29 14:18:49 +04:00
punpcklbw mm2,mm0 // mm2 = x1
punpcklbw mm3,mm0 // mm3 = x2
2007-06-30 02:00:49 +04:00
add ecx,step_src
add edx,step_src
2007-06-29 14:18:49 +04:00
#if 1
2007-06-30 02:00:49 +04:00
// catmull-rom cubic
// "scheduled" to try to spread stuff out early
// also the final shift by two has been optimized up earlier
// (which means we really only get 6-7 good bits)
2007-06-29 14:18:49 +04:00
psubw mm4,mm1 // mm4 = x3-x0
movq mm5,mm2 // mm5 = x1
movq mm6,mm3 // mm6 = x2
2007-06-30 02:00:49 +04:00
psubw mm5,mm3 // mm5 = x1-x2
2007-06-29 14:18:49 +04:00
paddw mm3,mm1 // mm3 = x0+x2
2007-06-30 02:00:49 +04:00
psubw mm6,mm1 // mm6 = c
psubw mm3,mm2 // mm3 = x0+x2-d/2
pmullw mm5,three // mm5 = 3*(x1-x2)
2007-06-29 14:18:49 +04:00
psubw mm3,mm2 // mm3 = x0+x2-d
pmulhw mm6,mm7 // mm6 = c*t
2007-06-30 02:00:49 +04:00
paddw mm5,mm4 // mm5 = a
psubw mm3,mm5 // mm3 = b
psllw mm5,2 // mm5 = a(15.1)
psllw mm3,1 // mm3 = b
pmulhw mm5,mm7 // mm5 = a*t
paddw mm6,mm2 // mm6 = c*t+d
paddw mm5,mm3 // mm5 = a*t + b
pmulhw mm5,mm7 // mm5 = a*t^2+b*t
pmulhw mm5,mm7 // mm5 = a*t^3+b*t^2
2007-06-29 14:18:49 +04:00
paddw mm5,mm6
packuswb mm5,mm5
movd [edi],mm5
#else
// unknown spline type from: http://local.wasp.uwa.edu.au/~pbourke/other/interpolation/
psubw mm4,mm3 // mm4 = x3-x2
psubw mm4,mm1 // mm4 = x3-x2-x0
paddw mm4,mm2 // mm4 = a0 = x3-x2-x0+x1
psubw mm3,mm1 // mm3 = a2 = x2-x0
psubw mm1,mm2 // mm1 = x0-x1
psubw mm1,mm4 // mm1 = a1 = x0-x1-a0
// mm2 = a3 = y1
psllw mm4,3
pmulhw mm4,mm7
pmulhw mm4,mm7
pmulhw mm4,mm7
psllw mm1,2
pmulhw mm1,mm7
pmulhw mm1,mm7
psllw mm3,1
pmulhw mm3,mm7
paddw mm1,mm2
paddw mm1,mm3
paddw mm1,mm4
packuswb mm1,mm1
movd [edi],mm1
#endif
add edi,step_dest
dec esi
jnz looptop
emms
pop edi
pop esi
pop edx
pop ecx
pop ebx
pop eax
}
}
#else
2007-06-30 01:43:15 +04:00
static int cubic(int x0, int x1, int x2, int x3, int lerp8)
{
int a = 3*(x1-x2) + (x3-x0);
int d = x1+x1;
int c = x2 - x0;
int b = -a-d + x0+x2;
int res = a * lerp8 + (b << 8);
res = (res * lerp8);
res = ((res >> 16) + c) * lerp8;
res = ((res >> 8) + d) >> 1;
if (res < 0) res = 0; else if (res > 255) res = 255;
return res;
}
2007-06-30 04:25:15 +04:00
static void cubic_interpolate_span(Color *dest, Color *x0, Color *x1, Color *x2, Color *x3, int lerp8, int step_dest, int step_src, int len)
2007-06-29 02:57:49 +04:00
{
2007-06-29 14:18:49 +04:00
int i;
for (i=0; i < len; ++i) {
int r,g,b,a;
r = cubic(R(*x0),R(*x1),R(*x2),R(*x3),lerp8);
g = cubic(G(*x0),G(*x1),G(*x2),G(*x3),lerp8);
b = cubic(B(*x0),B(*x1),B(*x2),B(*x3),lerp8);
a = cubic(A(*x0),A(*x1),A(*x2),A(*x3),lerp8);
*dest = RGBA(r,g,b,a);
x0 += step_src>>2;
x1 += step_src>>2;
x2 += step_src>>2;
x3 += step_src>>2;
dest += step_dest>>2;
}
2007-06-29 02:57:49 +04:00
}
2007-06-29 14:18:49 +04:00
#endif
#define PLUS(x,y) ((uint32 *) ((uint8 *) (x) + (y)))
2007-06-29 02:57:49 +04:00
2007-06-30 01:43:15 +04:00
struct
{
Image *src;
Image *out;
int out_len;
int delta;
} cubic_work;
2007-06-29 14:18:49 +04:00
#define CUBIC_BLOCK 32
2007-06-30 04:25:15 +04:00
void * cubic_interp_1d_x_work(int n)
2007-06-29 02:57:49 +04:00
{
2007-06-30 01:43:15 +04:00
int out_w = cubic_work.out_len;
int x,dx,i,j,k,k_start, k_end;
Image *out = cubic_work.out;
Image *src = cubic_work.src;
dx = cubic_work.delta;
k_start = out->y * n / resize_threads;
k_end = out->y * (n+1) / resize_threads;
for (k=k_start; k < k_end; k += CUBIC_BLOCK) {
int k2 = stb_min(k+CUBIC_BLOCK, k_end);
2007-06-29 02:57:49 +04:00
x = 0;
for (i=0; i < out_w; ++i) {
2007-06-29 14:18:49 +04:00
uint32 *data = (uint32 *) (src->pixels + k*src->stride);
uint32 *dest = (uint32 *) (out->pixels + k*out->stride) + i;
2007-06-29 02:57:49 +04:00
int xp = (x >> 16);
int xw = (x >> 8) & 255;
2007-06-29 14:18:49 +04:00
if (xp == 0) {
2007-06-30 04:25:15 +04:00
cubic_interpolate_span(dest, data+xp,data+xp,data+xp+1,data+xp+2,xw,out->stride,src->stride,k2-k);
2007-06-29 14:18:49 +04:00
} else if (xp >= src->x - 2) {
if (xp == src->x-1) {
for (j=k; j < k2; ++j) {
dest[0] = data[xp];
data = PLUS(data, src->stride);
dest = PLUS(dest , out->stride);
}
} else {
2007-06-30 04:25:15 +04:00
cubic_interpolate_span(dest, data+xp-1,data+xp,data+xp+1,data+xp+1,xw,out->stride,src->stride,k2-k);
2007-06-29 14:18:49 +04:00
}
} else {
2007-06-30 04:25:15 +04:00
cubic_interpolate_span(dest, data+xp-1,data+xp,data+xp+1,data+xp+2,xw,out->stride,src->stride,k2-k);
2007-06-29 14:18:49 +04:00
}
2007-06-29 02:57:49 +04:00
x += dx;
}
}
2007-06-30 01:43:15 +04:00
return NULL;
2007-06-29 02:57:49 +04:00
}
2007-06-30 04:25:15 +04:00
Image *cubic_interp_1d_x(Image *src, int out_w)
2007-06-30 01:43:15 +04:00
{
int i;
cubic_work.out = bmp_alloc(out_w, src->y);
cubic_work.delta = (src->x-1)*65536 / (out_w-1);
cubic_work.src = src;
cubic_work.out_len = out_w;
barrier();
if (resize_threads == 1) {
2007-06-30 04:25:15 +04:00
cubic_interp_1d_x_work(0);
2007-06-30 01:43:15 +04:00
} else {
stb_sync_set_target(resize_merge, resize_threads);
2007-06-30 01:43:15 +04:00
for (i=1; i < resize_threads; ++i)
stb_workq_reach(resize_workers, (stb_thread_func) cubic_interp_1d_x_work, (void *) i, NULL, resize_merge);
2007-06-30 04:25:15 +04:00
cubic_interp_1d_x_work(0);
stb_sync_reach_and_wait(resize_merge);
2007-06-30 01:43:15 +04:00
}
return cubic_work.out;
}
2007-06-30 04:25:15 +04:00
Image *cubic_interp_1d_y_work(int n)
2007-06-29 02:57:49 +04:00
{
2007-06-30 01:43:15 +04:00
int y,dy,j,j_end;
int out_h = cubic_work.out_len;
Image *src = cubic_work.src;
Image *out = cubic_work.out;
dy = cubic_work.delta;
j = out_h * n / resize_threads;
j_end = out_h * (n+1) / resize_threads;
y = j * dy;
for (; j < j_end; ++j,y+=dy) {
2007-06-29 14:18:49 +04:00
uint32 *dest = (uint32 *) (out->pixels + j*out->stride);
2007-06-29 02:57:49 +04:00
int yp = (y >> 16);
uint8 yw = (y >> 8);
uint32 *data1 = (uint32 *) (src->pixels + yp*src->stride);
uint32 *data2 = PLUS(data1,src->stride);
uint32 *data0 = (yp > 0) ? PLUS(data1, - src->stride) : data1;
uint32 *data3 = (yp < src->y-2) ? PLUS(data2, src->stride) : data2;
2007-06-30 04:25:15 +04:00
cubic_interpolate_span(dest, data0, data1, data2, data3, yw, 4,4,out->x);
2007-06-29 02:57:49 +04:00
}
2007-06-30 01:43:15 +04:00
return NULL;
2007-06-29 02:57:49 +04:00
}
2007-06-30 04:25:15 +04:00
Image *cubic_interp_1d_y(Image *src, int out_h)
2007-06-29 02:57:49 +04:00
{
2007-06-30 01:43:15 +04:00
int i;
cubic_work.src = src;
cubic_work.out = bmp_alloc(src->x, out_h);
cubic_work.delta = ((src->y-1)*65536-1) / (out_h-1);
cubic_work.out_len = out_h;
2007-06-29 02:57:49 +04:00
2007-06-30 01:43:15 +04:00
if (resize_threads == 1) {
2007-06-30 04:25:15 +04:00
cubic_interp_1d_y_work(0);
2007-06-30 01:43:15 +04:00
} else {
barrier();
stb_sync_set_target(resize_merge, resize_threads);
2007-06-30 01:43:15 +04:00
for (i=1; i < resize_threads; ++i)
stb_workq_reach(resize_workers, (stb_thread_func) cubic_interp_1d_y_work, (void *) i, NULL, resize_merge);
2007-06-30 04:25:15 +04:00
cubic_interp_1d_y_work(0);
stb_sync_reach_and_wait(resize_merge);
2007-06-29 02:57:49 +04:00
}
2007-06-30 01:43:15 +04:00
return cubic_work.out;
2007-06-29 02:57:49 +04:00
}
// downsampling
2007-06-30 04:25:15 +04:00
Image *downsample_half(Image *src)
2007-06-29 02:57:49 +04:00
{
int i,j, w,h;
Image *res;
w = src->x>>1;
h = src->y>>1;
res = bmp_alloc(w,h);
for (j=0; j < h; j += 1) {
Color *src0 = (uint32*)(src->pixels + 2*j * src->stride);
Color *src1 = PLUS(src0, src->stride);
for (i=0; i < w; i += 1) {
Color *dest = (uint32*)(res->pixels + j * res->stride + i*BPP);
// this will cause quantization of flat-colored regions, thus can
// cause banding in very slow gradients
*dest = ((src0[0] >> 2) & 0x3f3f3f3f) +
((src0[1] >> 2) & 0x3f3f3f3f) +
((src1[0] >> 2) & 0x3f3f3f3f) +
((src1[1] >> 2) & 0x3f3f3f3f);
src0 += 2;
src1 += 2;
}
}
return res;
}
2007-06-30 04:25:15 +04:00
Image *downsample_two_thirds(Image *src)
2007-06-29 02:57:49 +04:00
{
int i,j, w,h;
Image *res;
w = src->x/3 * 2;
h = src->y/3 * 2;
res = bmp_alloc(w, h);
for (j=0; j+1 < h; j += 2) {
Color *src0 = (uint32*)(src->pixels + 3*(j>>1) * src->stride);
Color *src1 = PLUS(src0, src->stride);
Color *src2 = PLUS(src1, src->stride);
// use (2/3,1/3) and (1/3,2/3), which amounts to:
// A B C W X
// D E F ->
// G H I Y Z
// W = A*4/9 + B * 2/9 + D * 2/9 + E * 1/9
// for speed, approximate as A*3/8 + B*2/8 + D*2/8 + E*1/8
for (i=0; i+1 < w; i += 2) {
Color *dest = (uint32*)(res->pixels + j * res->stride + i*BPP);
dest[0] = ((src0[0] >> 1) & 0x7f7f7f7f) - ((src0[0] >> 3) & 0x1f1f1f1f)
+ ((src0[1] >> 2) & 0x3f3f3f3f) + ((src1[0] >> 2) & 0x3f3f3f3f)
+ ((src1[1] >> 3) & 0x1f1f1f1f);
dest[1] = ((src0[2] >> 1) & 0x7f7f7f7f) - ((src0[2] >> 3) & 0x1f1f1f1f)
+ ((src0[1] >> 2) & 0x3f3f3f3f) + ((src1[2] >> 2) & 0x3f3f3f3f)
+ ((src1[1] >> 3) & 0x1f1f1f1f);
dest = PLUS(dest,res->stride);
dest[0] = ((src2[0] >> 1) & 0x7f7f7f7f) - ((src2[0] >> 3) & 0x1f1f1f1f)
+ ((src2[1] >> 2) & 0x3f3f3f3f) + ((src1[0] >> 2) & 0x3f3f3f3f)
+ ((src1[1] >> 3) & 0x1f1f1f1f);
dest[1] = ((src2[2] >> 1) & 0x7f7f7f7f) - ((src2[2] >> 3) & 0x1f1f1f1f)
+ ((src2[1] >> 2) & 0x3f3f3f3f) + ((src1[2] >> 2) & 0x3f3f3f3f)
+ ((src1[1] >> 3) & 0x1f1f1f1f);
src0 += 3;
src1 += 3;
src2 += 3;
}
}
return res;
}
2007-06-29 14:18:49 +04:00
Image *grScaleBitmap(Image *src, int gx, int gy, Image *dest)
2007-06-29 02:57:49 +04:00
{
Image *to_free, *res;
2007-06-29 14:18:49 +04:00
int upsample=FALSE;
2007-06-29 02:57:49 +04:00
to_free = NULL;
2007-06-29 14:18:49 +04:00
// check if we're scaling up
if (gx > src->x || gy > src->y) {
upsample = TRUE;
} else {
// maybe should do something smarter here, like find the
// nearest box size, instead of repetitive powers of two
while (gx <= (src->x >> 1) && gy <= (src->y >> 1)) {
2007-06-30 04:25:15 +04:00
src = downsample_half(src);
2007-06-29 14:18:49 +04:00
if (to_free) imfree(to_free);
to_free = src;
}
2007-06-29 02:57:49 +04:00
2007-06-29 14:18:49 +04:00
if (gx < src->x * 0.666666f && gy < src->y * 0.666666f) {
2007-06-30 04:25:15 +04:00
src = downsample_two_thirds(src);
2007-06-29 14:18:49 +04:00
if (to_free) imfree(to_free);
to_free = src;
}
2007-06-29 02:57:49 +04:00
}
if (gx == src->x && gy == src->y) {
if (to_free)
res = src;
else {
res = bmp_alloc(src->x, src->y);
memcpy(res->pixels, src->pixels, res->y * res->stride);
return res;
}
2007-06-29 14:18:49 +04:00
} else if (upsample ? upsample_cubic : downsample_cubic) {
2007-06-30 04:25:15 +04:00
res = cubic_interp_1d_y(src, gy);
2007-06-29 02:57:49 +04:00
if (to_free) imfree(to_free);
to_free = res;
2007-06-30 04:25:15 +04:00
res = cubic_interp_1d_x(res, gx);
2007-06-29 02:57:49 +04:00
imfree(to_free);
2007-06-29 14:18:49 +04:00
} else {
#if 1
2007-06-30 04:25:15 +04:00
image_resize_bilinear(dest, src);
2007-06-29 14:18:49 +04:00
if (to_free) imfree(to_free);
res = NULL;
#else
res = grScaleBitmapX(src, gx);
2007-06-29 02:57:49 +04:00
if (to_free) imfree(to_free);
to_free = res;
2007-06-29 14:18:49 +04:00
res = grScaleBitmapY(res, gy);
2007-06-29 02:57:49 +04:00
imfree(to_free);
2007-06-29 14:18:49 +04:00
#endif
2007-06-29 02:57:49 +04:00
}
return res;
}
2007-06-30 02:00:49 +04:00
#endif // BPP==4
2007-06-29 02:57:49 +04:00
2007-06-29 14:18:49 +04:00
void image_resize(Image *dest, Image *src)
2007-06-25 07:45:23 +04:00
{
2007-06-29 14:18:49 +04:00
#if BPP==3
2007-06-30 04:25:15 +04:00
image_resize_bilinear(dest, src);
2007-06-29 02:57:49 +04:00
#else
int j;
Image *temp;
2007-06-29 14:18:49 +04:00
temp = grScaleBitmap(src, dest->x, dest->y, dest);
if (temp) {
for (j=0; j < dest->y; ++j)
memcpy(dest->pixels + j*dest->stride, temp->pixels + j*temp->stride, BPP*dest->x);
imfree(temp);
}
2007-06-29 02:57:49 +04:00
#endif
2007-06-29 14:18:49 +04:00
}
2007-07-11 21:09:01 +04:00
char imv_failure_buffer[1024];
char *imv_failure_string;
2007-07-11 21:09:01 +04:00
static char *imv_failure_reason(void)
{
return imv_failure_string;
}
2007-07-11 21:09:01 +04:00
#ifdef USE_FREEIMAGE
// FreeImage types
typedef int FREE_IMAGE_FORMAT;
typedef struct FIBITMAP FIBITMAP;
typedef struct FIMEMORY FIMEMORY;
typedef void (*FreeImage_OutputMessageFunction)(FREE_IMAGE_FORMAT fif, const char *msg);
#define fitype(x) typedef __declspec(dllimport) x __stdcall
// FreeImage functions that we're going to import;
fitype(void) freeimage_setoutputmessage(FreeImage_OutputMessageFunction omf);
static freeimage_setoutputmessage *FreeImage_SetOutputMessage;
fitype(FIMEMORY *) freeimage_openmemory(uint8 *data, unsigned size);
static freeimage_openmemory *FreeImage_OpenMemory;
fitype(void) freeimage_closememory(FIMEMORY *stream);
static freeimage_closememory *FreeImage_CloseMemory;
fitype(int) freeimage_seekmemory(FIMEMORY *stream, long offset, int origin);
static freeimage_seekmemory *FreeImage_SeekMemory;
fitype(FIBITMAP *) freeimage_loadfrommemory(FREE_IMAGE_FORMAT fif, FIMEMORY *fi, int flags);
static freeimage_loadfrommemory *FreeImage_LoadFromMemory;
fitype(void) freeimage_converttorawbits(BYTE *bits, FIBITMAP *dib, int pitch, unsigned bpp, unsigned red_mask, unsigned green_mask, unsigned blue_mask, BOOL topdown);
static freeimage_converttorawbits *FreeImage_ConvertToRawBits;
typedef __declspec(dllimport) void __stdcall freeimage_unload(FIBITMAP *dib);
static freeimage_unload *FreeImage_Unload;
typedef __declspec(dllimport) unsigned __stdcall freeimage_getwidth(FIBITMAP *dib);
static freeimage_getwidth *FreeImage_GetWidth;
typedef __declspec(dllimport) unsigned __stdcall freeimage_getheight(FIBITMAP *dib);
static freeimage_getheight *FreeImage_GetHeight;
typedef __declspec(dllimport) FREE_IMAGE_FORMAT __stdcall freeimage_getfiletypefrommemory(FIMEMORY *fi, int size);
static freeimage_getfiletypefrommemory *FreeImage_GetFileTypeFromMemory;
typedef __declspec(dllimport) FREE_IMAGE_FORMAT __stdcall freeimage_getfiffromfilename(const char *filename);
static freeimage_getfiffromfilename *FreeImage_GetFIFFromFilename;
typedef __declspec(dllimport) BYTE *__stdcall freeimage_getbits(FIBITMAP *dib);
static freeimage_getbits *FreeImage_GetBits;
typedef __declspec(dllimport) int __stdcall freeimage_istransparent(FIBITMAP *dib);
static freeimage_istransparent *FreeImage_IsTransparent;
static void
FreeImageErrorHandler(FREE_IMAGE_FORMAT fif, const char *message)
{
strcpy(imv_failure_buffer, message);
imv_failure_string = imv_failure_buffer;
2007-07-11 21:09:01 +04:00
}
static HINSTANCE FreeImageDLL;
FARPROC fifunc(char *str)
{
FARPROC p = GetProcAddress(FreeImageDLL, str);
if (p == NULL)
FreeImagePresent = FALSE; // if something doesn't load, bail!
return p;
}
static int LoadFreeImage(void)
{
static int InitializationAttempted;
if (InitializationAttempted) return FreeImagePresent;
InitializationAttempted = TRUE;
FreeImagePresent = FALSE;
FreeImageDLL = LoadLibrary("FreeImage.dll");
if (!FreeImageDLL) return FreeImagePresent;
FreeImagePresent = TRUE;
FreeImage_ConvertToRawBits = (freeimage_converttorawbits *)fifunc("_FreeImage_ConvertToRawBits@32");
FreeImage_GetBits = (freeimage_getbits *)fifunc("_FreeImage_GetBits@4");
FreeImage_GetFIFFromFilename = (freeimage_getfiffromfilename *)fifunc("_FreeImage_GetFIFFromFilename@4");
FreeImage_GetFileTypeFromMemory = (freeimage_getfiletypefrommemory *)fifunc("_FreeImage_GetFileTypeFromMemory@8");
FreeImage_GetHeight = (freeimage_getheight *)fifunc("_FreeImage_GetHeight@4");
FreeImage_GetWidth = (freeimage_getwidth *)fifunc("_FreeImage_GetWidth@4");
FreeImage_LoadFromMemory = (freeimage_loadfrommemory *)fifunc("_FreeImage_LoadFromMemory@12");
FreeImage_SetOutputMessage = (freeimage_setoutputmessage *)fifunc("_FreeImage_SetOutputMessage@4");
FreeImage_Unload = (freeimage_unload *)fifunc("_FreeImage_Unload@4");
FreeImage_OpenMemory = (freeimage_openmemory *)fifunc("_FreeImage_OpenMemory@8");
FreeImage_CloseMemory = (freeimage_closememory *)fifunc("_FreeImage_CloseMemory@4");
FreeImage_SeekMemory = (freeimage_seekmemory *)fifunc("_FreeImage_SeekMemory@12");
FreeImage_IsTransparent = (freeimage_istransparent *)fifunc("_FreeImage_IsTransparent@4");
if (!FreeImagePresent) {
if (!only_stbi)
error("Invalid FreeImage.dll; disabling FreeImage support.");
} else
FreeImage_SetOutputMessage(FreeImageErrorHandler);
return FreeImagePresent;
2007-07-11 21:09:01 +04:00
}
uint8 *LoadImageWithFreeImage(FIMEMORY *fi, int *x, int *y, int *n, int n_req)
{
uint8 *Result = 0;
FREE_IMAGE_FORMAT FileFormat = FreeImage_GetFileTypeFromMemory(fi, 0);
FIBITMAP *Bitmap;
if(FileFormat == -1) {
// @TODO: propogate the filename to here?
// bail!
return NULL;
// FileFormat = FreeImage_GetFIFFromFilename(FromFilename);
}
FreeImage_SeekMemory(fi, 0, SEEK_SET);
Bitmap = FreeImage_LoadFromMemory(FileFormat, fi, 0);
if(Bitmap) {
int32 Width = FreeImage_GetWidth(Bitmap);
int32 Height = FreeImage_GetHeight(Bitmap);
2007-07-11 21:09:01 +04:00
Result = (uint8 *) malloc(Width * Height * BPP);
if(Result) {
int i;
FreeImage_ConvertToRawBits(Result, Bitmap, BPP*Width, BPP*8, 0xff0000,0x00ff00,0xff, FALSE);
for (i=0; i < Width*Height*BPP; i += BPP) {
uint8 t = Result[i];
Result[i] = Result[i+2];
Result[i+2] = t;
}
*x = Width;
*y = Height;
*n = FreeImage_IsTransparent(Bitmap) ? 4 : 3;
}
FreeImage_Unload(Bitmap);
}
return Result;
2007-07-11 21:09:01 +04:00
}
#endif
static uint8 *imv_decode_from_memory(uint8 *mem, int len, int *x, int *y, int *n, int n_req)
{
uint8 *res = NULL;
imv_failure_string = NULL;
res = stbi_load_from_memory(mem, len, x, y, n, n_req);
if (res) return res;
imv_failure_string = stbi_failure_reason();
2007-07-11 21:09:01 +04:00
#ifdef USE_FREEIMAGE
if (!only_stbi) {
if (FreeImagePresent) {
2007-07-11 21:09:01 +04:00
FIMEMORY *fi = FreeImage_OpenMemory(mem,len);
res = LoadImageWithFreeImage(fi, x, y, n, n_req);
FreeImage_CloseMemory(fi);
// if no error message is generated, because it's not a known type,
// we'll get the unknown-type message from stbi_failure_reason()
2007-07-11 21:09:01 +04:00
}
}
#endif
return res;
}