commenting, refactoring, add preferences for label

This commit is contained in:
nothings.org 2007-07-01 22:11:30 +00:00
parent 1ff2efa350
commit 36385459b4
7 changed files with 354 additions and 139 deletions

378
imv.c
View File

@ -45,7 +45,7 @@ void error(char *str) { MessageBox(NULL, str, "imv(stb) error", MB_OK); }
// OutputDebugString with varargs, can be compiled out
#ifdef _DEBUG
int do_debug;
int do_debug=1;
void ods(char *str, ...)
{
if (do_debug) {
@ -551,7 +551,7 @@ char helptext_left[] =
"CTRL-MINUS: zoom out\n"
"RIGHT, SPACE: next image\n"
"LEFT, BACKSPACE: previous image\n"
" CTRL-O: open image\n"
" (CTRL-) O: open image\n"
" P: change preferences\n"
" F: toggle frame\n"
"SHIFT-F: toggle white stripe in frame\n"
@ -577,11 +577,8 @@ char helptext_right[] =
// looks a little nicer so why not
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)
{
@ -591,7 +588,6 @@ void draw_nice(HDC hdc, char *text, RECT *rect, uint flags)
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);
}
@ -613,6 +609,20 @@ void set_error(volatile ImageFile *z)
}
HFONT label_font;
int label_font_height=12;
// build the font for the filename label
void build_label_font(void)
{
LOGFONT lf;
memset(&lf, 0, sizeof(lf));
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);
}
int show_frame = TRUE; // show border or not?
int show_label = FALSE; // display the help text or not
@ -1029,7 +1039,7 @@ void toggle_frame(void)
int best_lru = 0;
// when we change which file is the one being viewed/resized,
// call this function
// call this function to update our globals and fit to window
void update_source(ImageFile *q)
{
source = q->image;
@ -1251,7 +1261,7 @@ void queue_disk_command(DiskCommand *dc, int which, int make_current)
return;
}
//
// if it's not inactive and none of the above, it's an error
if (z->status != LOAD_inactive) {
if (make_current) {
set_error(z);
@ -1346,8 +1356,10 @@ void advance(int dir)
}
// ctrl-O, or initial command if no filename: run
// GetOpenFileName(), load the specified filelist,
//
// 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];
void open_file(void)
{
@ -1368,18 +1380,31 @@ void open_file(void)
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
#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.
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;
int zoom=0; // this is log2(zoom_factor)*2; that is, a 0=1x, 2=2x, 4=4x, 6=8x
// if we have a current image, use that
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
if (cur->x > source->x + FRAME*2 || cur->y > source->y + FRAME*2) {
for(;;) {
s = (float) pow(2, zoom/2.0f + 0.25f);
@ -1409,6 +1434,7 @@ void resize(int step)
y2 = int(y*s) + 2*FRAME;
} while (x2 == cur->x || y2 == cur->y);
} else {
// if no current image (e.g. an error), just resize relative to current in power-of-two steps
RECT rect;
GetAdjustedWindowRect(win, &rect);
x2 = rect.right - rect.left;
@ -1417,10 +1443,10 @@ void resize(int step)
x2 <<= 1, y2 <<= 1;
if (step < 0 && x2 >= 64 && y2 >= 64)
x2 >>= 1, y2 >>= 1;
}
{
// compute top left to keep same center
RECT rect;
GetAdjustedWindowRect(win, &rect);
x = (rect.left + rect.right)>>1;
@ -1433,6 +1459,7 @@ void resize(int step)
display_mode = zoom==0 ? DISPLAY_actual : DISPLAY_current;
}
// when mouse button is down, what mode are we in?
enum
{
MODE_none,
@ -1440,42 +1467,50 @@ enum
MODE_resize,
} dragmode;
#define setmode(x) (dragmode = x)
#define ismode(x) (dragmode == x)
#define setmode(x) (dragmode = (x))
#define ismode(x) (dragmode == (x))
#define anymode() !ismode(MODE_none)
static int ex,ey; // mousedown location relative to top left
static int ex2,ey2; // mousedown location relative to bottom right
static int wx,wy;
static int rx,ry,rx2,ry2;
static int rx,ry; // sides that are being resized
// compute the borders to the resize-vs-move regions (e.g. for mouse cursor)
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;
int w,h,w2;
GetClientRect(win, &rect);
// client dimensions
w = rect.right;
h = rect.bottom;
// size of resize regions is identical in both axes, so
// use smaller window size
w = stb_min(w,h);
// compute size of handles
w2 = w >> 4; h2 = h >> 4;
if (w2 < 12) {
w2 = w >> 2;
if (w2 < 4) w2 = w >> 1;
} else if (w2 > 100) w2 = 100;
if (h2 < 12) {
h2 = h >> 2;
if (h2 < 4) h2 = h >> 1;
} else if (h2 > 100) h2 = 100;
if (h2 < w2) w2 = h2;
if (w2 < h2) h2 = w2;
// 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 (w < 16) w2 = w >> 1;
else if (w < 200) w2 = w >> 2;
else if (w < 800) w2 = w >> 3;
else if (w < 1600) w2 = w >> 4;
else w2 = 100;
*x0 = w2;
*x1 = w - w2;
*y0 = h2;
*y1 = h - h2;
*y0 = w2;
*y1 = h - w2;
}
// static cursor cache of standard windows cursor for resizing
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
void set_cursor(int x, int y)
{
int x0,y0,x1,y1;
@ -1489,27 +1524,35 @@ void set_cursor(int x, int y)
else SetCursor(c_def);
}
// all windows mouse messages that involve the cursor position route here
// (i.e. all mouse messages except mousewheel)
void mouse(UINT ev, int x, int y)
{
switch (ev) {
case WM_LBUTTONDBLCLK:
toggle_display();
break;
case WM_LBUTTONDOWN:
// if we're not in drag/size (and how could we be?!?)
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
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;
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
if (rx || ry)
setmode(MODE_resize);
else
setmode(MODE_drag);
// capture the mouse until they let go
SetCapture(win);
// record the position of the mouse cursor relative to the window
// sides, so we can resize properly
GetAdjustedWindowRect(win, &rect);
ex = x;
ey = y;
@ -1517,48 +1560,64 @@ void mouse(UINT ev, int x, int y)
ey2 = y - (rect.bottom-rect.top);
}
break;
case WM_MOUSEMOVE:
switch(dragmode) {
default: assert(0);
case MODE_none:
break;
// in drag mode, a mouse move just moves the window
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;
GetAdjustedWindowRect(win, &rect);
assert(rx || ry);
display_mode = DISPLAY_current;
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
#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+LIMIT, rect.left+x-ex2);
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+LIMIT, rect.top+y-ey2);
if (ry > 0) rect.bottom = stb_max(rect.top +y-ey2, rect.top + LIMIT);
// then force the window to resize to the new rect
enqueue_resize(rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top);
break;
}
}
break;
case WM_RBUTTONUP:
// right mouse click when not modal exits
if (!anymode())
exit(0);
// otherwise, disrupt a modal operation
/* FALLTHROUGH */
case WM_LBUTTONUP:
ReleaseCapture();
setmode(MODE_none);
set_cursor(x,y);
set_cursor(x,y); // return cursor to normal setting
break;
}
}
static unsigned int physmem;
static unsigned int physmem; // available physical memory according to GlobalMemoryStatus
char *reg_root = "Software\\SilverSpaceship\\imv";
int reg_get(char *str, void *data, int len)
{
@ -1591,12 +1650,16 @@ int reg_set(char *str, void *data, int len)
return result;
}
// we use very short strings for these to avoid wasting space, since
// people shouldn't be mucking with them directly anyway!
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);
reg_set("lfs", &label_font_height, 4);
reg_set("label", &show_label, 4);
}
void reg_load(void)
@ -1604,16 +1667,15 @@ void reg_load(void)
int temp;
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;
}
static HWND dialog;
static LRESULT send_dialog(int id, UINT msg, WPARAM p1, LPARAM p2)
{
return SendMessage(GetDlgItem(dialog,id),msg,p1,p2);
}
static HWND dialog; // preferences dialog
// set an edit control's text from an integer
static void set_dialog_number(int id, int value)
{
char buffer[16];
@ -1621,6 +1683,7 @@ static void set_dialog_number(int id, int value)
SetWindowText(GetDlgItem(dialog, id), buffer);
}
// get an edit control's text as an integer
static int get_dialog_number(int id)
{
char buffer[32];
@ -1629,6 +1692,8 @@ static int get_dialog_number(int id)
return atoi(buffer);
}
// clamp an edit control's text into an integer range (and
// remove non-integer chacters)
static void dialog_clamp(int id, int low, int high)
{
int x = get_dialog_number(id);
@ -1638,39 +1703,52 @@ static void dialog_clamp(int id, int low, int high)
set_dialog_number(id,x);
}
extern unsigned char *rom_images[];
extern unsigned char *rom_images[]; // preference images
// preferences dialog windows procedure
BOOL CALLBACK PrefDlgProc(HWND hdlg, UINT imsg, WPARAM wparam, LPARAM lparam)
{
static Image *pref_image;
int i;
dialog = hdlg;
dialog = hdlg; // store dialog handle to not pass it to above functions
switch(imsg)
{
case WM_INITDIALOG: {
// pick a random preference image
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
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);
// copy preferences into dialog
SendMessage(GetDlgItem(hdlg, DIALOG_upsample), BM_SETCHECK, upsample_cubic, 0);
SendMessage(GetDlgItem(hdlg, DIALOG_showlabel), BM_SETCHECK, show_label, 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);
set_dialog_number(DIALOG_labelheight, label_font_height);
return TRUE;
}
case WM_PAINT: {
// 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
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);
@ -1680,7 +1758,8 @@ BOOL CALLBACK PrefDlgProc(HWND hdlg, UINT imsg, WPARAM wparam, LPARAM lparam)
int k = LOWORD(wparam);
int n = HIWORD(wparam);
switch(k) {
// validate the dialog entries
// validate the dialog entries into range and numeric only, when
// they lose focus only
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);
@ -1688,15 +1767,24 @@ BOOL CALLBACK PrefDlgProc(HWND hdlg, UINT imsg, WPARAM wparam, LPARAM lparam)
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;
case IDOK: {
// user clicked ok... copy out current values to check for changes
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;
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);
// if alpha_background changed, clear the cache of any images that used it
if (memcmp(alpha_background, cur, 6)) {
stb_mutex_begin(cache_mutex);
for (i=0; i < MAX_CACHED_IMAGES; ++i) {
@ -1712,32 +1800,51 @@ BOOL CALLBACK PrefDlgProc(HWND hdlg, UINT imsg, WPARAM wparam, LPARAM lparam)
}
}
stb_mutex_end(cache_mutex);
// force a reload of the current image
advance(0);
}
// save the data out to the registry
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);
/* FALL THROUGH */
}
case IDCANCEL:
imfree(pref_image);
pref_image = NULL;
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;
}
return FALSE;
}
// missing VK definitions in old compiler
#ifndef VK_OEM_PLUS
#define VK_OEM_PLUS 0xbb
#define VK_OEM_MINUS 0xbd
#endif
#ifndef VK_SLASH
#define VK_SLASH 0xbf
#endif
// 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!
HINSTANCE inst;
int WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
@ -1751,6 +1858,11 @@ int WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
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'.
int best_lru=0,i;
volatile ImageFile *best = NULL;
for (i=0; i < MAX_CACHED_IMAGES; ++i) {
@ -1763,14 +1875,17 @@ int WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
}
}
}
if (best->status == LOAD_error_reading || best->status == LOAD_error_decoding) {
// if the most recently-browsed and displayable image is an error, show it
if (best->status == LOAD_error_reading || best->status == LOAD_error_decoding)
set_error(best);
}
break;
}
case WM_APP_DECODED: {
// scan the filelist for the highest-lru, decoded image
// 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.
int i;
ImageFile *best = NULL;
for (i=0; i < stb_arr_len(fileinfo); ++i) {
@ -1787,13 +1902,15 @@ int WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
o(("Post-decode, found a best image, better than any before.\n"));
update_source(best);
}
// since we've decoded a new image, our cache might be too big,
// so try flushing it
flush_cache(FALSE);
break;
}
case WM_MOUSEWHEEL: {
int zdelta = (short) HIWORD(wParam);
// ignore scaling factor and step 1 by 1
// ignore wheel scaling factor and step 1 by 1
if (zdelta > 0) resize(1);
if (zdelta < 0) resize(-1);
break;
@ -1809,6 +1926,7 @@ int WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
return 0;
case WM_SETCURSOR: {
// there's no GetCursorPos for the client window?
POINT p;
if (GetCursorPos(&p)) {
RECT rect;
@ -1911,6 +2029,7 @@ int WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
resize(-1);
break;
case 'O':
case MY_CTRL | 'O':
open_file();
break;
@ -1934,8 +2053,11 @@ int WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
return 1;
}
// number of threads to use in resizer
#define MAX_RESIZE 4
int resize_threads;
// whether 'cur' (the resized image currently displayed) actually comes from 'source'
int cur_is_current(void)
{
if (!cur_filename) return FALSE;
@ -1943,43 +2065,41 @@ int cur_is_current(void)
return !strcmp(cur_filename, source_c->filename);
}
#define MAX_RESIZE 4
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
char filenamebuffer[4096];
int argc;
char **argv = stb_tokens_quoted(lpCmdLine, " ", &argc);
char filenamebuffer[4096];
MEMORYSTATUS mem;
MSG msg;
WNDCLASSEX wndclass;
HWND hWnd;
int image_x, image_y;
// initial loaded image
int image_x, image_y, image_n;
unsigned char *image_data;
int image_n;
inst = hInstance;
resize_threads = stb_processor_count();
// concatenate the version number onto the help text, because
// we can't do this statically with the current build process
strcat(helptext_center, VERSION);
if (resize_threads > MAX_RESIZE) resize_threads = MAX_RESIZE;
#ifdef _DEBUG
do_debug = IsDebuggerPresent();
#endif
// determine the number of threads to use in the resizer
resize_threads = stb_min(stb_processor_count(), MAX_RESIZE);
// compute the amount of physical memory to set a guess for the cache size
GlobalMemoryStatus(&mem);
if (mem.dwTotalPhys == 0) --mem.dwTotalPhys;
physmem = mem.dwTotalPhys;
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)
reg_load();
strcat(helptext_center, VERSION);
/* Register the frame class */
// create the main window class
memset(&wndclass, 0, sizeof(wndclass));
wndclass.cbSize = sizeof(wndclass);
wndclass.style = CS_OWNDC | CS_DBLCLKS;
@ -1991,27 +2111,22 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
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);
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;
{
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);
}
build_label_font();
srand(time(NULL));
if (argc < 1) {
// if run with no arguments, get an initial filename
OPENFILENAME o;
memset(&o, 0, sizeof(o));
o.lStructSize = sizeof(o);
@ -2023,31 +2138,48 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
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];
}
// allocate worker threads
resize_workers = stb_workq_new(resize_threads, resize_threads * 4);
cache_mutex = stb_mutex_new();
disk_command_queue = stb_sem_new(1,1);
decode_queue = stb_sem_new(1,1);
decode_mutex = stb_mutex_new();
// load initial image
image_data = stbi_load(filename, &image_x, &image_y, &image_n, BPP);
if (image_data == NULL) {
// 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 *why = stbi_failure_reason();
char buffer[512];
sprintf(buffer, "'%s': %s", filename, why);
error(buffer);
exit(0);
}
// 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,1);
disk_command_queue = stb_sem_new(1,1);
// go ahead and start the other tasks
stb_create_thread(diskload_task, NULL);
stb_create_thread(decode_task, NULL);
// create the source image by converting the image data to BGR,
// pre-blending alpha
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
cache[0].status = LOAD_available;
cache[0].image = source;
cache[0].lru = lru_stamp++;
@ -2056,13 +2188,15 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
stb_sdict_add(file_cache, filename, (void *) &cache[0]);
source_c = (ImageFile *) &cache[0];
{
int x,y;
int w2 = source->x+FRAME*2, h2 = source->y+FRAME*2;
int w,h;
// determine an initial window size, and resize
ideal_window_size(w2,h2, &w,&h, &x,&y);
// if the size exactly matches, don't resize, just copy
if (w == source->x+FRAME*2 && h == source->y+FRAME*2) {
display_error[0] = 0;
cur = bmp_alloc(image_x + FRAME*2, image_y + FRAME*2);
@ -2086,25 +2220,26 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
}
cur_filename = strdup(filename);
wx = w;
wy = h;
// create the window
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
// display the window
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
InvalidateRect(hWnd, NULL, TRUE);
for(;;) {
// if we're not currently resizing, start a resize
// if we're not currently resizing, and there's a resize request
if (qs.w && pending_resize.size.w == 0) {
if (source) {
// is the image we're showing the image to resize, and does the size match?
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);
@ -2118,38 +2253,61 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
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
if (!PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE)) {
// no messages, so check for pending activity
// no messages, so check for a resize completing
if (pending_resize.size.w) {
// there's a resize pending, so don't block
if (!pending_resize.image) {
// resize isn't done, so sleep for a bit and try again
Sleep(10);
continue;
} else {
// resize is done
HDC hdc;
o(("Finished resize\n"));
imfree(cur);
// reclaim ownership of the image from the resizer
pending_resize.image_c->status = LOAD_available;
// free the current image we're about to write over
imfree(cur);
cur = pending_resize.image;
display_error[0] = 0;
cur_filename = pending_resize.filename;
pending_resize.filename = NULL;
// clear error messages
display_error[0] = 0;
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
SetWindowPos(hWnd,NULL,pending_resize.size.x, pending_resize.size.y, pending_resize.size.w, pending_resize.size.h, SWP_NOZORDER);
// clear the resize request info
pending_resize.filename = NULL;
barrier();
pending_resize.size.w = 0;
// paint the window
hdc = GetDC(win);
display(hWnd, hdc);
ReleaseDC(win, hdc);
}
// restart from the top
continue;
}
}
}
// we can get rid of this with peek-with-remove, surely?
if (!GetMessage(&msg, NULL, 0, 0))
return msg.wParam;
@ -2158,12 +2316,10 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
}
}
#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)
/////////////////////////////////////////////////////////////////////////////
//
// Everything from here on down just does image resizing
//
typedef struct {
short i;

BIN
imv.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 766 B

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1,11 +1,22 @@
Version 0.57
Version 0.91: Beta 2 (2007-07-01)
* feature: allow changing the label font size
* internal: various refactorings to clean up the code
* bugfix: finish commenting code (except resizer)
* bugfix: fix tiny leak closing the preferences dialog with the close button
Version 0.90: Beta 1 (2007-06-30)
* bugfix: user-friendlier error messages
* feature: save preferences to registry
* feature: preferences dialog
Version 0.57 (2007-06-29)
* feature: cubic image resampling
* bugfix: advancing to pre-loaded image then retreated to previous image
* bugfix: occasional error when advancing to image that was about to be decoded
* bugfix: commented about 75% of code
* bugfix: fix logic for fitting large images onscreen to not stop a few pixels short
Version 0.56
Version 0.56 (2007-06-27)
* bugfix: stb_image wouldn't load jpegtran output (which is invalid JFIF)
Version 0.55 (2007-06-27)

View File

@ -2,9 +2,7 @@
= Release =
This is an alpha version, targetting the eventual milestones:
version 0.90 - beta
This is a beta version, targetting the eventual milestones:
version 1.00 - stable release

64
pref.rc
View File

@ -52,15 +52,15 @@ END
// Dialog
//
IDD_pref DIALOG DISCARDABLE 0, 0, 161, 141
IDD_pref DIALOG DISCARDABLE 0, 0, 161, 171
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "imv(stb) preferences"
FONT 8, "MS Sans Serif"
BEGIN
DEFPUSHBUTTON "OK",IDOK,23,119,50,14
PUSHBUTTON "Cancel",IDCANCEL,83,119,50,14
DEFPUSHBUTTON "OK",IDOK,23,149,50,14
PUSHBUTTON "Cancel",IDCANCEL,83,149,50,14
CONTROL "High-quality resizing",DIALOG_upsample,"Button",
BS_AUTOCHECKBOX | WS_TABSTOP,24,86,76,10
BS_AUTOCHECKBOX | WS_TABSTOP,23,116,76,10
EDITTEXT DIALOG_r1,57,50,19,12,ES_RIGHT | ES_AUTOHSCROLL
EDITTEXT DIALOG_g1,79,50,19,12,ES_RIGHT | ES_AUTOHSCROLL
EDITTEXT DIALOG_b1,101,50,19,12,ES_RIGHT | ES_AUTOHSCROLL
@ -71,10 +71,14 @@ BEGIN
EDITTEXT DIALOG_r2,57,63,19,12,ES_RIGHT | ES_AUTOHSCROLL
EDITTEXT DIALOG_g2,79,63,19,12,ES_RIGHT | ES_AUTOHSCROLL
EDITTEXT DIALOG_b2,101,63,19,12,ES_RIGHT | ES_AUTOHSCROLL
EDITTEXT DIALOG_cachesize,11,100,21,13,ES_AUTOHSCROLL
LTEXT "Size of image cache in megabytes",IDC_STATIC,36,102,107,
EDITTEXT DIALOG_cachesize,10,130,21,13,ES_AUTOHSCROLL
LTEXT "Size of image cache in megabytes",IDC_STATIC,35,133,107,
8
LTEXT "",DIALOG_image,45,7,70,28
EDITTEXT DIALOG_labelheight,11,99,20,12,ES_AUTOHSCROLL
LTEXT "Label font height",IDC_STATIC,35,101,104,10
CONTROL "Show filename label",DIALOG_showlabel,"Button",
BS_AUTOCHECKBOX | WS_TABSTOP,23,87,120,10
END
@ -91,7 +95,7 @@ BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 154
TOPMARGIN, 7
BOTTOMMARGIN, 134
BOTTOMMARGIN, 164
END
END
#endif // APSTUDIO_INVOKED
@ -105,6 +109,52 @@ END
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_ICON1 ICON DISCARDABLE "imv.ico"
#ifndef _MAC
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,1
PRODUCTVERSION 1,0,0,1
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "Comments", "\0"
VALUE "CompanyName", " Sean Barrett / nothings.org\0"
VALUE "FileDescription", "imv(stb) image viewer\0"
VALUE "FileVersion", "1, 0, 0, 1\0"
VALUE "InternalName", "stb_imv\0"
VALUE "LegalCopyright", "GPL; Copyright © 2007\0"
VALUE "LegalTrademarks", "\0"
VALUE "OriginalFilename", "stb_imv.exe\0"
VALUE "PrivateBuild", "\0"
VALUE "ProductName", "imv(stb)\0"
VALUE "ProductVersion", "1, 0, 0, 1\0"
VALUE "SpecialBuild", "\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
#endif // !_MAC
#endif // English (U.S.) resources
/////////////////////////////////////////////////////////////////////////////

View File

@ -44,7 +44,7 @@ RSC=rc.exe
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c
# ADD CPP /nologo /G6 /MD /Ze /W3 /GX /O2 /Oy /Ob1 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /FD /c
# ADD CPP /nologo /G6 /MD /W3 /GX /O2 /Ob0 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /FD /c
# SUBTRACT CPP /YX
# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32

View File

@ -1 +1 @@
set VERSION="0.57"
set VERSION="0.91"