stb-imv/imv.c

2797 lines
82 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 0x0400
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 */
#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-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;
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
// size of border in pixels
2007-06-25 07:45:23 +04:00
#define FRAME 3
2007-06-29 02:57:49 +04:00
// location within frame of secondary border
2007-06-25 07:45:23 +04:00
#define FRAME2 (FRAME >> 1)
2007-06-29 02:57:49 +04:00
// color of secondary border
2007-06-25 07:45:23 +04:00
#define GREY 192
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;
2007-06-25 07:45:23 +04:00
BITMAPINFOHEADER b;
int result;
memset(&b, 0, sizeof(b));
b.biSize = sizeof(b);
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;
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-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-06-25 12:57:39 +04:00
data = stbi_load_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
2007-06-25 07:45:23 +04:00
f->error = strdup(stbi_failure_reason());
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-06-30 04:25:15 +04:00
// declare with extra bytes so we can print the version number into it
char helptext_center[88] =
2007-06-27 23:42:35 +04:00
"imv(stb)\n"
"Copyright 2007 Sean Barret\n"
"http://code.google.com/p/stb-imv\n"
"version "
2007-06-26 08:17:12 +04:00
;
char helptext_left[] =
2007-06-27 23:42:35 +04:00
"\n\n\n\n"
2007-06-30 23:21:21 +04:00
" ESC: exit\n"
" 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-06-30 23:21:21 +04:00
" CTRL-O: open image\n"
" P: change preferences\n"
" F: toggle frame\n"
2007-06-27 23:42:35 +04:00
"SHIFT-F: toggle white stripe in frame\n"
"CTRL-F: toggle both\n"
2007-06-30 23:21:21 +04:00
" L: toggle filename label\n"
2007-06-27 23:42:35 +04:00
"F1, H, ?: help"
2007-06-26 08:17:12 +04:00
;
char helptext_right[] =
2007-06-27 23:42:35 +04:00
"\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-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)
{
#if 1
int i,j;
SetTextColor(hdc, RGB(80,80,80));
//for (i=-1; i <= 1; i += 1)
//for (j=-1; j <= 1; j += 1)
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);
}
#endif
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;
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;
char *name = cur_filename ? cur_filename : "(none)";
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-06-29 14:18:49 +04:00
static 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-06-27 23:42:35 +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
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!
int cur_loc = -1; // offset within the current list of files
2007-06-25 07:45:23 +04:00
2007-06-30 04:25:15 +04:00
// information about files we have currently loaded
2007-06-25 07:45:23 +04:00
struct
{
char *filename;
int lru;
} *fileinfo;
2007-06-30 04:25:15 +04:00
// stb_sdict is a string dictionary (strings as keys, void * as values)
// dictionary mapping filenames (key) to cached images (ImageFile *)
// @TODO: do we need this, or can it be in fileinfo?
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-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
}
image_files = stb_readdir_files_mask(path_to_file, "*.jpg;*.jpeg;*.png;*.bmp");
if (image_files == NULL) error("Error: couldn't read directory.");
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
//
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-06-30 04:25:15 +04:00
// ctrl-O, or initial command if no filename: run
// GetOpenFileName(), load the specified filelist,
//
static char filenamebuffer[4096];
void open_file(void)
{
OPENFILENAME o;
memset(&o, 0, sizeof(o));
o.lStructSize = sizeof(o);
o.lpstrFilter = "Image Files\0*.jpg;*.jpeg;*.png;*.bmp\0";
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);
}
2007-06-25 07:45:23 +04:00
#define int(x) ((int) (x))
void resize(int step)
{
// first characterize the current size relative to the raw size
int x = source->x, y = source->y;
float s;
int x2,y2;
int zoom=0;
2007-06-27 01:22:01 +04:00
if (cur) {
if (cur->x > source->x + FRAME*2 || cur->y > source->y + FRAME*2) {
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 {
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
}
{
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
}
enum
{
MODE_none,
MODE_drag,
MODE_resize,
} dragmode;
#define setmode(x) (dragmode = x)
#define ismode(x) (dragmode == x)
#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
2007-06-25 07:45:23 +04:00
static int wx,wy;
static int rx,ry,rx2,ry2;
static void cursor_regions(int *x0, int *y0, int *x1, int *y1)
{
RECT rect;
int w,h,w2,h2;
GetWindowRect(win, &rect);
w = rect.right - rect.left;
h = rect.bottom - rect.top;
// compute size of handles
w2 = w >> 4; h2 = h >> 4;
if (w2 < 12) {
w2 = w >> 2;
if (w2 < 4) w2 = w >> 1;
2007-06-26 05:00:05 +04:00
} else if (w2 > 100) w2 = 100;
2007-06-25 07:45:23 +04:00
if (h2 < 12) {
h2 = h >> 2;
if (h2 < 4) h2 = h >> 1;
2007-06-26 05:00:05 +04:00
} else if (h2 > 100) h2 = 100;
2007-06-25 07:45:23 +04:00
if (h2 < w2) w2 = h2;
if (w2 < h2) h2 = w2;
*x0 = w2;
*x1 = w - w2;
*y0 = h2;
*y1 = h - h2;
}
HCURSOR c_def, c_ne_sw, c_e_w, c_nw_se, c_n_s;
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);
}
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 (!anymode()) {
RECT rect;
int x0,y0,x1,y1;
cursor_regions(&x0,&y0,&x1,&y1);
rx = ry = 0;
if (x < x0) rx = -1;
if (x > x1) rx = 1;
if (y < y0) ry = -1;
if (y > y1) ry = 1;
if (rx || ry)
setmode(MODE_resize);
else
setmode(MODE_drag);
SetCapture(win);
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;
case WM_MOUSEMOVE:
switch(dragmode) {
default: assert(0);
case MODE_none:
break;
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);
set_cursor(x,y);
break;
}
case MODE_resize: {
RECT rect;
2007-06-27 23:42:35 +04:00
GetAdjustedWindowRect(win, &rect);
2007-06-25 07:45:23 +04:00
assert(rx || ry);
2007-06-26 05:00:05 +04:00
display_mode = DISPLAY_current;
2007-06-25 07:45:23 +04:00
#define LIMIT 16
2007-06-26 05:00:05 +04:00
if (rx < 0) rect.left = stb_min(rect.left+x-ex, rect.right-LIMIT);
2007-06-25 07:45:23 +04:00
if (rx > 0) rect.right = stb_max(rect.left+LIMIT, rect.left+x-ex2);
if (ry < 0) rect.top = stb_min(rect.top+y-ey, rect.bottom-LIMIT);
if (ry > 0) rect.bottom = stb_max(rect.top+LIMIT, rect.top+y-ey2);
enqueue_resize(rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top);
break;
}
}
break;
case WM_RBUTTONUP:
if (!anymode())
exit(0);
// otherwise, disrupt a modal operation
/* FALLTHROUGH */
case WM_LBUTTONUP:
ReleaseCapture();
setmode(MODE_none);
set_cursor(x,y);
break;
}
}
2007-06-30 23:21:21 +04:00
static unsigned int physmem;
char *reg_root = "Software\\SilverSpaceship\\imv";
int reg_get(char *str, void *data, int len)
{
static char buffer[128];
int result=0;
HKEY z=0;
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, reg_root, 0, KEY_READ, &z))
{
unsigned int type;
if (ERROR_SUCCESS == RegQueryValueEx(z, str, 0, &type, data, &len))
if (type == REG_BINARY)
result = 1;
}
if (z)
RegCloseKey(z);
return result;
}
int reg_set(char *str, void *data, int len)
{
int result = 0;
HKEY z=0;
if (ERROR_SUCCESS == RegCreateKeyEx(HKEY_LOCAL_MACHINE, reg_root, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &z, NULL))
{
if (ERROR_SUCCESS == RegSetValueEx(z, str, 0, REG_BINARY, data, len))
result = 1;
}
if (z)
RegCloseKey(z);
return result;
}
void reg_save(void)
{
int temp = max_cache_bytes >> 20;
reg_set("ac", &alpha_background, 6);
reg_set("up", &upsample_cubic, 4);
reg_set("cache", &temp, 4);
}
void reg_load(void)
{
int temp;
reg_get("ac", &alpha_background, 6);
reg_get("up", &upsample_cubic, 4);
if (reg_get("cache", &temp, 4))
max_cache_bytes = temp << 20;
}
static HWND dialog;
static LRESULT send_dialog(int id, UINT msg, WPARAM p1, LPARAM p2)
{
return SendMessage(GetDlgItem(dialog,id),msg,p1,p2);
}
static void set_dialog_number(int id, int value)
{
char buffer[16];
sprintf(buffer, "%d", value);
SetWindowText(GetDlgItem(dialog, id), buffer);
}
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);
}
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[];
BOOL CALLBACK PrefDlgProc(HWND hdlg, UINT imsg, WPARAM wparam, LPARAM lparam)
{
static Image *pref_image;
int i;
dialog = hdlg;
switch(imsg)
{
case WM_INITDIALOG: {
int n = ((rand() >> 6) % 3);
{
int x,y,i,j,k;
uint8 *data = stbi_load_from_memory(rom_images[n],2000,&x,&y,NULL,1);
pref_image = bmp_alloc(x,y);
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];
}
send_dialog(DIALOG_upsample, BM_SETCHECK, upsample_cubic, 0);
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);
return TRUE;
}
case WM_PAINT: {
RECT z;
int x,y;
HWND pic = GetDlgItem(hdlg, DIALOG_image);
GetWindowRect(pic,&z);
InvalidateRect(pic,NULL,TRUE);
UpdateWindow(pic);
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);
break;
}
case WM_COMMAND: {
int k = LOWORD(wparam);
int n = HIWORD(wparam);
switch(k) {
// validate the dialog entries
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 IDOK: {
unsigned char cur[6];
int up = upsample_cubic;
memcpy(cur, alpha_background, 6);
// 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;
upsample_cubic = send_dialog(DIALOG_upsample, BM_GETCHECK,0,0) == BST_CHECKED;
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);
advance(0);
}
reg_save();
/* FALL THROUGH */
}
case IDCANCEL:
imfree(pref_image);
pref_image = NULL;
EndDialog(hdlg,0);
return TRUE;
}
break;
}
}
return FALSE;
}
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
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:
{
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 (best->status == LOAD_error_reading || best->status == LOAD_error_decoding) {
set_error(best);
}
break;
}
2007-06-25 07:45:23 +04:00
case WM_APP_DECODED: {
// scan the filelist for the highest-lru, decoded image
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);
}
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 scaling factor and step 1 by 1
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: {
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;
}
#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);
code += wParam;
switch (wParam) {
case 27:
exit(0);
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-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-06-27 23:42:35 +04:00
case 'F' | MY_SHIFT:
extra_border = !extra_border;
if (cur) frame(cur);
InvalidateRect(win, NULL, FALSE);
break;
case 'F':
toggle_frame();
break;
case 'F' | MY_CTRL:
toggle_frame();
extra_border = show_frame;
if (cur) frame(cur);
break;
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 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;
}
int resize_threads;
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-06-30 02:00:49 +04:00
#define MAX_RESIZE 4
2007-06-25 07:45:23 +04:00
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
char filenamebuffer[4096];
2007-06-25 07:45:23 +04:00
int argc;
char **argv = stb_tokens_quoted(lpCmdLine, " ", &argc);
MEMORYSTATUS mem;
MSG msg;
WNDCLASSEX wndclass;
HWND hWnd;
int image_x, image_y;
unsigned char *image_data;
int image_n;
2007-06-30 23:21:21 +04:00
inst = hInstance;
2007-06-25 07:45:23 +04:00
resize_threads = stb_processor_count();
2007-06-30 02:00:49 +04:00
if (resize_threads > MAX_RESIZE) resize_threads = MAX_RESIZE;
2007-06-30 01:43:15 +04:00
2007-06-26 05:00:05 +04:00
#ifdef _DEBUG
do_debug = IsDebuggerPresent();
2007-06-26 05:00:05 +04:00
#endif
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;
2007-06-30 23:21:21 +04:00
reg_load();
2007-06-27 01:22:01 +04:00
strcat(helptext_center, VERSION);
2007-06-25 07:45:23 +04:00
/* Register the frame class */
memset(&wndclass, 0, sizeof(wndclass));
wndclass.cbSize = sizeof(wndclass);
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 = GetStockObject(BLACK_BRUSH);
wndclass.lpszMenuName = szAppName;
wndclass.lpszClassName = szAppName;
wndclass.hIconSm = LoadIcon(hInstance, szAppName);
c_def = LoadCursor(NULL, IDC_ARROW);
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);
if (!RegisterClassEx(&wndclass))
return FALSE;
2007-06-27 23:42:35 +04:00
{
LOGFONT lf;
memset(&lf, 0, sizeof(lf));
lf.lfHeight = 12;
lf.lfOutPrecision = OUT_TT_PRECIS; // prefer truetype to raster fonts
strcpy(lf.lfFaceName, "Times New Roman");
label_font = CreateFontIndirect(&lf);
}
2007-06-30 23:21:21 +04:00
srand(time(NULL));
2007-06-27 23:42:35 +04:00
if (argc < 1) {
OPENFILENAME o;
memset(&o, 0, sizeof(o));
o.lStructSize = sizeof(o);
o.lpstrFilter = "Image Files\0*.jpg;*.jpeg;*.png;*.bmp\0";
o.lpstrFile = filenamebuffer;
filenamebuffer[0] = 0;
o.nMaxFile = sizeof(filenamebuffer);
if (!GetOpenFileName(&o))
return 0;
filename = filenamebuffer;
} else {
filename = argv[0];
}
2007-06-25 07:45:23 +04:00
resize_workers = stb_workq_new(resize_threads, resize_threads * 4);
cache_mutex = stb_mutex_new();
2007-06-25 07:45:23 +04:00
disk_command_queue = stb_sem_new(1,1);
decode_queue = stb_sem_new(1,1);
decode_mutex = stb_mutex_new();
2007-06-25 07:45:23 +04:00
image_data = stbi_load(filename, &image_x, &image_y, &image_n, BPP);
2007-06-25 07:45:23 +04:00
if (image_data == NULL) {
char *why = stbi_failure_reason();
char buffer[512];
sprintf(buffer, "'%s': %s", filename, why);
2007-06-25 07:45:23 +04:00
error(buffer);
exit(0);
}
stb_fixpath(filename);
stb_splitpath(path_to_file, filename, STB_PATH);
stb_create_thread(diskload_task, NULL);
stb_create_thread(decode_task, NULL);
2007-06-25 07:45:23 +04:00
source = malloc(sizeof(*source));
make_image(source, image_x, image_y, image_data, image_n);
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;
ideal_window_size(w2,h2, &w,&h, &x,&y);
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
wx = w;
wy = h;
hWnd = CreateWindow(szAppName, displayName,
WS_POPUP,
x,y, w, h,
NULL, NULL, hInstance, NULL);
}
if (!hWnd)
return FALSE;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
InvalidateRect(hWnd, NULL, TRUE);
for(;;) {
// if we're not currently resizing, start a resize
if (qs.w && pending_resize.size.w == 0) {
2007-06-29 14:18:49 +04:00
if (source) {
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
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-06-25 07:45:23 +04:00
}
qs.w = 0;
}
if (!PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE)) {
// no messages, so check for pending activity
if (pending_resize.size.w) {
// there's a resize pending, so don't block
if (!pending_resize.image) {
Sleep(10);
} else {
2007-06-29 02:57:49 +04:00
HDC hdc;
2007-06-29 14:18:49 +04:00
o(("Finished resize\n"));
2007-06-25 07:45:23 +04:00
imfree(cur);
2007-06-29 14:18:49 +04:00
pending_resize.image_c->status = LOAD_available;
2007-06-25 07:45:23 +04:00
cur = pending_resize.image;
2007-06-27 01:22:01 +04:00
display_error[0] = 0;
2007-06-26 05:00:05 +04:00
cur_filename = pending_resize.filename;
pending_resize.filename = NULL;
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;
}
2007-06-25 07:45:23 +04:00
SetWindowPos(hWnd,NULL,pending_resize.size.x, pending_resize.size.y, pending_resize.size.w, pending_resize.size.h, SWP_NOZORDER);
barrier();
pending_resize.size.w = 0;
2007-06-29 02:57:49 +04:00
hdc = GetDC(win);
display(hWnd, hdc);
ReleaseDC(win, hdc);
2007-06-25 07:45:23 +04:00
}
continue;
}
}
if (!GetMessage(&msg, NULL, 0, 0))
return msg.wParam;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
#define MAGIC (1.5 * (1 << 26) * (1 << 26))
double temp;
#define FAST_FLOAT_TO_INT(x) ((q->temp = (x) + MAGIC), *(int *)&q->temp)
#define toint(x) ((int) (x)) // FAST_FLOAT_TO_INT(x)
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;
int done;
} 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;
}
}
q->done = TRUE;
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;
q[i].done = FALSE;
j1 = j0;
}
if (resize_threads == 1) {
image_resize_work(q);
} else {
barrier();
for (i=1; i < resize_threads; ++i)
stb_workq(resize_workers, image_resize_work, q+i, NULL);
image_resize_work(q);
for(;;) {
for (i=1; i < resize_threads; ++i)
if (!q[i].done)
break;
if (i == resize_threads) break;
Sleep(10);
}
}
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
barrier();
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 {
2007-06-30 02:00:49 +04:00
volatile void *which[MAX_RESIZE];
2007-06-30 01:43:15 +04:00
for (i=0; i < resize_threads; ++i)
which[i] = (void *) 1;
barrier();
for (i=1; i < resize_threads; ++i)
2007-06-30 04:25:15 +04:00
stb_workq(resize_workers, (stb_thread_func) cubic_interp_1d_x_work, (void *) i, which+i);
cubic_interp_1d_x_work(0);
2007-06-30 01:43:15 +04:00
for(;;) {
for (i=1; i < resize_threads; ++i)
if (which[i])
break;
if (i == resize_threads) break;
Sleep(10);
}
}
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;
barrier();
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 {
2007-06-30 02:00:49 +04:00
volatile void *which[MAX_RESIZE];
2007-06-30 01:43:15 +04:00
for (i=0; i < resize_threads; ++i)
which[i] = (void *) 1;
barrier();
for (i=1; i < resize_threads; ++i)
2007-06-30 04:25:15 +04:00
stb_workq(resize_workers, (stb_thread_func) cubic_interp_1d_y_work, (void *) i, which+i);
cubic_interp_1d_y_work(0);
2007-06-30 01:43:15 +04:00
for(;;) {
for (i=1; i < resize_threads; ++i)
if (which[i])
break;
if (i == resize_threads) break;
Sleep(10);
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
}