comments, minor cleanup

This commit is contained in:
nothings.org 2007-06-30 00:25:15 +00:00
parent 7e00a81516
commit 914b3290bf
3 changed files with 359 additions and 178 deletions

418
imv.c
View File

@ -16,9 +16,8 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
// Set section alignment to be nice and small // Set section alignment to minimize alignment overhead
#pragma comment(linker, "/FILEALIGN:0x200") #pragma comment(linker, "/FILEALIGN:0x200")
//#pragma comment(linker, "/OPT:NOWIN98")
#define _WIN32_WINNT 0x0400 #define _WIN32_WINNT 0x0400
#include <windows.h> #include <windows.h>
@ -99,6 +98,7 @@ HWND win;
// lightweight SetDIBitsToDevice() wrapper // lightweight SetDIBitsToDevice() wrapper
// (once upon a time this was a platform-independent, hence the name) // (once upon a time this was a platform-independent, hence the name)
// if 'dim' is set, draw it darkened
void platformDrawBitmap(HDC hdc, int x, int y, unsigned char *bits, int w, int h, int stride, int dim) void platformDrawBitmap(HDC hdc, int x, int y, unsigned char *bits, int w, int h, int stride, int dim)
{ {
int i; int i;
@ -111,13 +111,22 @@ void platformDrawBitmap(HDC hdc, int x, int y, unsigned char *bits, int w, int h
b.biBitCount=BPP*8; b.biBitCount=BPP*8;
b.biWidth = stride/BPP; b.biWidth = stride/BPP;
b.biHeight = -h; // tell windows the bitmap is stored top-to-bottom b.biHeight = -h; // tell windows the bitmap is stored top-to-bottom
if (dim) if (dim)
// divide the brightness of each channel by two... (if BPP==3, this
// does 4 pixels every 3 iterations)
for (i=0; i < stride*h; i += 4) for (i=0; i < stride*h; i += 4)
*(uint32 *)(bits+i) = (*(uint32 *)(bits+i) >> 1) & 0x7f7f7f7f; *(uint32 *)(bits+i) = (*(uint32 *)(bits+i) >> 1) & 0x7f7f7f7f;
result = SetDIBitsToDevice(hdc, x,y, w,abs(h), 0,0, 0,abs(h), bits, (BITMAPINFO *) &b, DIB_RGB_COLORS); result = SetDIBitsToDevice(hdc, x,y, w,abs(h), 0,0, 0,abs(h), bits, (BITMAPINFO *) &b, DIB_RGB_COLORS);
if (result == 0) { if (result == 0) {
DWORD e = GetLastError(); DWORD e = GetLastError();
} }
// 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?
if (dim) if (dim)
for (i=0; i < stride*h; i += 4) for (i=0; i < stride*h; i += 4)
*(uint32 *)(bits+i) = (*(uint32 *)(bits+i) << 1); *(uint32 *)(bits+i) = (*(uint32 *)(bits+i) << 1);
@ -290,8 +299,8 @@ void *diskload_task(void *p)
} }
} }
// given raw decoded data, make it into a proper Image (e.g. creating a // 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) // windows-compatible bitmap with 4-byte aligned rows and BGR color order)
void make_image(Image *z, int image_x, int image_y, uint8 *image_data, int image_n) void make_image(Image *z, int image_x, int image_y, uint8 *image_data, int image_n)
{ {
int i,j,k=0; int i,j,k=0;
@ -370,7 +379,7 @@ start:
// if there is a best one, it's possible that while iterating // 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 // 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 // still ready to decode. (Of course it ALSO could have changed
// lru priority and other such, or not be the best anymore, but // lru priority and other such, so not be the best anymore, but
// it's no big deal to get that wrong since it's close.) // it's no big deal to get that wrong since it's close.)
stb_mutex_begin(cache_mutex); stb_mutex_begin(cache_mutex);
{ {
@ -483,6 +492,7 @@ void frame(Image *z)
} }
} }
// free an image and its contents
void imfree(Image *x) void imfree(Image *x)
{ {
if (x) { if (x) {
@ -509,8 +519,12 @@ Image *cur;
// the filename for the currently displayed image // the filename for the currently displayed image
char *cur_filename; char *cur_filename;
int show_help=0; int show_help=0;
int downsample_cubic = 0;
int upsample_cubic = TRUE;
char helptext_center[128] =
// declare with extra bytes so we can print the version number into it
char helptext_center[88] =
"imv(stb)\n" "imv(stb)\n"
"Copyright 2007 Sean Barret\n" "Copyright 2007 Sean Barret\n"
"http://code.google.com/p/stb-imv\n" "http://code.google.com/p/stb-imv\n"
@ -523,10 +537,11 @@ char helptext_left[] =
"ALT-ENTER: toggle size\n" "ALT-ENTER: toggle size\n"
"CTRL-PLUS: zoom in\n" "CTRL-PLUS: zoom in\n"
"CTRL-MINUS: zoom out\n" "CTRL-MINUS: zoom out\n"
"LEFT, SPACE: next image\n" "RIGHT, SPACE: next image\n"
"RIGHT, BACKSPACE: previous image\n" "LEFT, BACKSPACE: previous image\n"
"CTRL-O: open image\n" "CTRL-O: open image\n"
"F: toggle frame\n" "F: toggle frame\n"
"C: toggle resize quality\n"
"SHIFT-F: toggle white stripe in frame\n" "SHIFT-F: toggle white stripe in frame\n"
"CTRL-F: toggle both\n" "CTRL-F: toggle both\n"
"L: toggle filename label\n" "L: toggle filename label\n"
@ -587,7 +602,9 @@ void set_error(volatile ImageFile *z)
HFONT label_font; HFONT label_font;
int show_frame = TRUE; // show border or not? int show_frame = TRUE; // show border or not?
int show_label = FALSE; // int show_label = FALSE; // display the help text or not
// WM_PAINT, etc.
void display(HWND win, HDC hdc) void display(HWND win, HDC hdc)
{ {
RECT rect,r2; RECT rect,r2;
@ -630,6 +647,7 @@ void display(HWND win, HDC hdc)
r2.right = x+cur->x; r2.right = x+cur->x;
r2.top = y + cur->y; FillRect(hdc, &r2, b); r2.top = y + cur->y; FillRect(hdc, &r2, b);
// should we show the name of the file?
if (show_label) { if (show_label) {
SIZE size; SIZE size;
RECT z; RECT z;
@ -638,7 +656,7 @@ void display(HWND win, HDC hdc)
if (label_font) old = SelectObject(hdc, label_font); if (label_font) old = SelectObject(hdc, label_font);
// get rect around label so we can draw it ourselves, because // get rect around label so we can draw it ourselves, because
// the DrawText() one is lame // the DrawText() one is poorly sized
GetTextExtentPoint32(hdc, name, strlen(name), &size); GetTextExtentPoint32(hdc, name, strlen(name), &size);
z.left = rect.left+1; z.left = rect.left+1;
@ -662,8 +680,9 @@ void display(HWND win, HDC hdc)
// build rect of correct height // build rect of correct height
box = rect; box = rect;
box.top = stb_max((h - h2) >> 1, 0); box.top = stb_max((h - h2) >> 1, 0);
box.bottom = box.top + h2; //box.bottom = box.top + h2;
// expand on left & right so following code is well behaved // expand on left & right so following code is well behaved
// (we're centered anyway, so the exact numbers don't matter)
box.left -= 200; box.right += 200; box.left -= 200; box.right += 200;
// draw center text // draw center text
@ -819,34 +838,96 @@ void GetAdjustedWindowRect(HWND win, RECT *rect)
} }
} }
// compute the size we'd prefer this window to be at // 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); 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;
}
}
enum enum
{ {
DISPLAY_actual, DISPLAY_actual, // display the image 1:1, or fullscreen if larger than screen
DISPLAY_current, DISPLAY_current, // display the image in the current window's size
DISPLAY__num, DISPLAY__num,
}; };
int display_mode; int display_mode;
// 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.
void size_to_current(int maximize) void size_to_current(int maximize)
{ {
int w2,h2; int w2,h2;
int w,h,x,y; int w,h,x,y;
Image *z = source;
w2 = source->x+FRAME*2, h2 = source->y+FRAME*2; // the 1:1 actual size WITH frame
w2 = source->x+FRAME*2;
h2 = source->y+FRAME*2;
switch (display_mode) { switch (display_mode) {
case DISPLAY_actual: { case DISPLAY_actual: {
int cx,cy; int cx,cy;
RECT rect; RECT rect;
// given the actual size, compute the ideal window size
// (which is either 1:1 or fullscreen) and center point
ideal_window_size(w2,h2, &w,&h, &x,&y); ideal_window_size(w2,h2, &w,&h, &x,&y);
// get the desktop size
cx = GetSystemMetrics(SM_CXSCREEN); cx = GetSystemMetrics(SM_CXSCREEN);
cy = GetSystemMetrics(SM_CYSCREEN); cy = GetSystemMetrics(SM_CYSCREEN);
// if the window fits on the desktop
if (w <= cx && h <= cy) { if (w <= cx && h <= cy) {
// try to use the current center point, as much as possible
GetAdjustedWindowRect(win, &rect); GetAdjustedWindowRect(win, &rect);
x = (rect.right + rect.left - w) >> 1; x = (rect.right + rect.left - w) >> 1;
y = (rect.top + rect.bottom - h) >> 1; y = (rect.top + rect.bottom - h) >> 1;
@ -855,12 +936,15 @@ void size_to_current(int maximize)
} }
break; break;
} }
case DISPLAY_current: case DISPLAY_current:
if (maximize) { if (maximize) {
// fullscreen, plus the frame around the edge
x = y = -FRAME; x = y = -FRAME;
w = GetSystemMetrics(SM_CXSCREEN) + FRAME*2; w = GetSystemMetrics(SM_CXSCREEN) + FRAME*2;
h = GetSystemMetrics(SM_CYSCREEN) + FRAME*2; h = GetSystemMetrics(SM_CYSCREEN) + FRAME*2;
} else { } else {
// just use the current window
RECT rect; RECT rect;
GetAdjustedWindowRect(win, &rect); GetAdjustedWindowRect(win, &rect);
x = rect.left; x = rect.left;
@ -871,26 +955,36 @@ void size_to_current(int maximize)
break; break;
} }
// if the image is 1:1, we don't need to resize, so skip
// queueing and all that and just build it
if (w == w2 && h == h2) { if (w == w2 && h == h2) {
int j; int j;
unsigned char *p = z->pixels; unsigned char *p = source->pixels;
// free the current image
imfree(cur); imfree(cur);
free(cur_filename); free(cur_filename);
cur = bmp_alloc(z->x + FRAME*2, z->y + FRAME*2);
display_error[0] = 0; // build the new one
cur = bmp_alloc(w2,h2);
cur_filename = strdup(source_c->filename); cur_filename = strdup(source_c->filename);
// build a frame around the data
frame(cur); frame(cur);
{ // copy the raw data in
for (j=0; j < z->y; ++j) { for (j=0; j < source->y; ++j) {
unsigned char *q = cur->pixels + (j+FRAME)*cur->stride + FRAME*BPP; unsigned char *q = cur->pixels + (j+FRAME)*cur->stride + FRAME*BPP;
memcpy(q, p, z->x*BPP); memcpy(q, p, source->x*BPP);
p += z->x*BPP; p += source->x*BPP;
}
} }
// no error for this image
display_error[0] = 0;
// if they don't want the frame, remove it now
if (!show_frame) x+=FRAME,y+=FRAME,w-=FRAME*2,h-=FRAME*2; if (!show_frame) x+=FRAME,y+=FRAME,w-=FRAME*2,h-=FRAME*2;
// move/show it
MoveWindow(win, x,y,w,h, TRUE); MoveWindow(win, x,y,w,h, TRUE);
InvalidateRect(win, NULL, FALSE); InvalidateRect(win, NULL, FALSE);
} else { } else {
// not 1:1; it requires resizing, so queue a resize request
qs.x = x; qs.x = x;
qs.y = y; qs.y = y;
qs.w = w; qs.w = w;
@ -898,6 +992,8 @@ void size_to_current(int maximize)
} }
} }
// when the user toggles the frame on and off, toggle the flag
// and update the window size as appropriate
void toggle_frame(void) void toggle_frame(void)
{ {
RECT rect; RECT rect;
@ -917,74 +1013,110 @@ void toggle_frame(void)
SetWindowPos(win, NULL, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, SWP_NOCOPYBITS|SWP_NOOWNERZORDER); SetWindowPos(win, NULL, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, SWP_NOCOPYBITS|SWP_NOOWNERZORDER);
} }
// 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
void update_source(ImageFile *q) void update_source(ImageFile *q)
{ {
Image *z = q->image; source = q->image;
source = z;
source_c = q; source_c = q;
o(("Making %s (%d) current\n", q->filename, q->lru));
if (q->lru > best_lru)
best_lru = q->lru;
if (z) if (source)
size_to_current(FALSE); size_to_current(FALSE); // don't maximize
} }
// toggle between the two main display modes
void toggle_display(void) void toggle_display(void)
{ {
if (source) { if (source) {
display_mode = (display_mode + 1) % DISPLAY__num; display_mode = (display_mode + 1) % DISPLAY__num;
size_to_current(TRUE); size_to_current(TRUE); // _DO_ maximize if DISPLAY_current
} }
} }
char path_to_file[4096], *filename; // manage the list of files in the current directory
char **image_files; // note that this is totally detached from the image cache;
int cur_loc = -1; // 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
// information about files we have currently loaded
struct struct
{ {
char *filename; char *filename;
int lru; int lru;
} *fileinfo; } *fileinfo;
// 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?
stb_sdict *file_cache; stb_sdict *file_cache;
// when switching/refreshing directories, free this data
void free_fileinfo(void) void free_fileinfo(void)
{ {
int i; int i;
for (i=0; i < stb_arr_len(fileinfo); ++i) for (i=0; i < stb_arr_len(fileinfo); ++i)
free(fileinfo[i].filename); free(fileinfo[i].filename); // allocated by stb_readdir
stb_arr_free(fileinfo); stb_arr_free(fileinfo);
fileinfo = NULL; fileinfo = NULL;
} }
// build a filelist for the current directory
void init_filelist(void) void init_filelist(void)
{ {
char *s = NULL; char **image_files; // stb_arr (dynamic array type) of filenames
char *to_free = NULL;
int i; int i;
if (fileinfo) { if (fileinfo) {
filename = s = strdup(fileinfo[cur_loc].filename); // 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(); free_fileinfo();
} }
image_files = stb_readdir_files_mask(path_to_file, "*.jpg;*.jpeg;*.png;*.bmp"); image_files = stb_readdir_files_mask(path_to_file, "*.jpg;*.jpeg;*.png;*.bmp");
if (image_files == NULL) error("Error: couldn't read directory."); if (image_files == NULL) error("Error: couldn't read directory.");
cur_loc = 0; // given the array of filenames, build an equivalent fileinfo array
stb_arr_setlen(fileinfo, stb_arr_len(image_files)); stb_arr_setlen(fileinfo, stb_arr_len(image_files));
// 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;
for (i=0; i < stb_arr_len(image_files); ++i) { for (i=0; i < stb_arr_len(image_files); ++i) {
fileinfo[i].filename = image_files[i]; fileinfo[i].filename = image_files[i];
fileinfo[i].lru = 0; fileinfo[i].lru = 0;
if (!stricmp(image_files[i], filename)) if (!stricmp(image_files[i], filename))
cur_loc = i; cur_loc = i;
} }
if (s) free(s);
// 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);
// free the stb_readdir() array, but not the filenames themselves
stb_arr_free(image_files);
} }
// current lru timestamp
int lru_stamp=1; int lru_stamp=1;
// maximum size of the cache
int max_cache_bytes = 256 * (1 << 20); // 256 MB; one 5MP image is 20MB int max_cache_bytes = 256 * (1 << 20); // 256 MB; one 5MP image is 20MB
// minimum number of cache entries
#define MIN_CACHE 3 // always keep 3 images cached, to allow prefetching #define MIN_CACHE 3 // always keep 3 images cached, to allow prefetching
// compare the lru timestamps in two cached images, with extra indirection
int ImageFilePtrCompare(const void *p, const void *q) int ImageFilePtrCompare(const void *p, const void *q)
{ {
ImageFile *a = *(ImageFile **) p; ImageFile *a = *(ImageFile **) p;
@ -992,11 +1124,18 @@ int ImageFilePtrCompare(const void *p, const void *q)
return (a->lru < b->lru) ? -1 : (a->lru > b->lru); return (a->lru < b->lru) ? -1 : (a->lru > b->lru);
} }
// 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
void flush_cache(int locked) void flush_cache(int locked)
{ {
int limit; int limit = MAX_CACHED_IMAGES - MIN_CACHE; // maximum images to cache
volatile ImageFile *list[MAX_CACHED_IMAGES]; volatile ImageFile *list[MAX_CACHED_IMAGES];
int i, total=0, occupied_slots=0, n=0; int i, total=0, occupied_slots=0, n=0;
// count number of images in use, and size they're using
for (i=0; i < MAX_CACHED_IMAGES; ++i) { for (i=0; i < MAX_CACHED_IMAGES; ++i) {
volatile ImageFile *z = &cache[i]; volatile ImageFile *z = &cache[i];
if (z->status != LOAD_unused) if (z->status != LOAD_unused)
@ -1008,23 +1147,24 @@ void flush_cache(int locked)
total += z->len; total += z->len;
} }
list[n++] = z; list[n++] = z;
} } // if main doesn't own, don't worry about it... so we may underestimate sometimes
} }
limit = MAX_CACHED_IMAGES - MIN_CACHE; if (!(total > max_cache_bytes || occupied_slots > limit))
if (total > max_cache_bytes || occupied_slots > limit) { return;
// sort by lru
qsort((void *) list, n, sizeof(*list), ImageFilePtrCompare); 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); if (!locked) stb_mutex_begin(cache_mutex);
for (i=0; i < n && occupied_slots > MIN_CACHE && (occupied_slots > limit || total > max_cache_bytes); ++i) { for (i=0; i < n && occupied_slots > MIN_CACHE && (occupied_slots > limit || total > max_cache_bytes); ++i) {
ImageFile p; if (MAIN_OWNS(list[i]) && list[i]->status != LOAD_unused) {
/* @TODO: this is totally squirrely and probably buggy. we need to // copy the rest of the data out for later use, then clear the existing data
* rethink how we can propose things to the disk loader and then ImageFile p = *list[i];
* later change our minds. is this mutex good enough?
*/
{
p.status = list[i]->status;
if (MAIN_OWNS(&p) && p.status != LOAD_unused) {
p = *list[i];
list[i]->bail = 1; // force disk to bail if it gets this -- can't happen? list[i]->bail = 1; // force disk to bail if it gets this -- can't happen?
list[i]->filename = NULL; list[i]->filename = NULL;
list[i]->filedata = NULL; list[i]->filedata = NULL;
@ -1032,10 +1172,12 @@ void flush_cache(int locked)
list[i]->image = NULL; list[i]->image = NULL;
list[i]->error = NULL; list[i]->error = NULL;
list[i]->status = LOAD_unused; list[i]->status = LOAD_unused;
}
} // we're done touching this entry (but not done with the data),
if (MAIN_OWNS(&p) && p.status != LOAD_unused) { // so release the mutex
if (!locked) stb_mutex_end(cache_mutex); if (!locked) stb_mutex_end(cache_mutex);
// now do the potentially slow stuff
o(("MAIN: freeing cache: %s\n", p.filename)); o(("MAIN: freeing cache: %s\n", p.filename));
stb_sdict_remove(file_cache, p.filename, NULL); stb_sdict_remove(file_cache, p.filename, NULL);
--occupied_slots; // occupied slots --occupied_slots; // occupied slots
@ -1047,24 +1189,30 @@ o(("MAIN: freeing cache: %s\n", p.filename));
if (p.filedata) free(p.filedata); if (p.filedata) free(p.filedata);
if (p.image) imfree(p.image); if (p.image) imfree(p.image);
if (p.error) free(p.error); 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); if (!locked) stb_mutex_begin(cache_mutex);
} }
} }
if (!locked) stb_mutex_end(cache_mutex); if (!locked) stb_mutex_end(cache_mutex);
o(("Reduced to %d megabytes\n", total >> 20)); o(("Reduced to %d megabytes\n", total >> 20));
} }
}
// keep an index within the 'fileinfo' array
int wrap(int z) int wrap(int z)
{ {
int n = stb_arr_len(image_files); int n = stb_arr_len(fileinfo);
if (z < 0) return z + n; if (z < 0) return z + n;
while (z >= n) z = z - n; while (z >= n) z = z - n;
return z; return z;
} }
// 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?)
void queue_disk_command(DiskCommand *dc, int which, int make_current) void queue_disk_command(DiskCommand *dc, int which, int make_current)
{ {
char *filename;
volatile ImageFile *z; volatile ImageFile *z;
// check if we already have it cached // check if we already have it cached
@ -1077,23 +1225,35 @@ void queue_disk_command(DiskCommand *dc, int which, int make_current)
// it's being loaded/decoded // it's being loaded/decoded
return; return;
} }
// it's waiting to be decoded, so doesn't need queueing
if (z->status == LOAD_reading_done) if (z->status == LOAD_reading_done)
return; return;
// it's already loaded
if (z->status == LOAD_available) { if (z->status == LOAD_available) {
if (make_current) if (make_current) {
o(("Hey look, make_currentdisk request for %s and it's ready to show!\n", z->filename));
update_source((ImageFile *) z); update_source((ImageFile *) z);
}
return; return;
} }
//
if (z->status != LOAD_inactive) { if (z->status != LOAD_inactive) {
if (make_current) { if (make_current) {
set_error(z); set_error(z);
} }
return; return;
} }
// it's a go, use z
// z->status == LOAD_inactive
// "fall through" to after the if, below
} else { } else {
int i,tried_again=FALSE; int i,tried_again=FALSE;
// find a cache slot
// didn't already have a cache slot, so find one; we called
// flush_cache() before calling this so a slot should be free
for (i=0; i < MAX_CACHED_IMAGES; ++i) for (i=0; i < MAX_CACHED_IMAGES; ++i)
if (cache[i].status == LOAD_unused) if (cache[i].status == LOAD_unused)
break; break;
@ -1101,6 +1261,8 @@ void queue_disk_command(DiskCommand *dc, int which, int make_current)
stb_fatal("Internal logic error: no free cache slots, but flush_cache() should free a few"); stb_fatal("Internal logic error: no free cache slots, but flush_cache() should free a few");
return; return;
} }
// allocate this slot and fill in the info
z = &cache[i]; z = &cache[i];
free(z->filename); free(z->filename);
assert(z->filedata == NULL); assert(z->filedata == NULL);
@ -1109,23 +1271,27 @@ void queue_disk_command(DiskCommand *dc, int which, int make_current)
z->status = LOAD_inactive; z->status = LOAD_inactive;
stb_sdict_add(file_cache, filename, (void *) z); stb_sdict_add(file_cache, filename, (void *) z);
} }
// now, take the z we already had, or just allocated, prep it for loading
assert(z->status == LOAD_inactive); assert(z->status == LOAD_inactive);
o(("MAIN: proposing %s\n", z->filename)); o(("MAIN: proposing %s\n", z->filename));
z->status = LOAD_inactive; z->status = LOAD_inactive; // we still own it for now
z->image = NULL; z->image = NULL;
z->bail = 0; z->bail = 0;
z->lru = fileinfo[which].lru; z->lru = fileinfo[which].lru; // pass lru value through
// and now really put it on the command list
dc->files[dc->num_files++] = (ImageFile *) z; dc->files[dc->num_files++] = (ImageFile *) z;
} }
// step through the current file list
void advance(int dir) void advance(int dir)
{ {
DiskCommand dc; DiskCommand dc;
int i; int i;
if (image_files == NULL) if (fileinfo == NULL)
init_filelist(); init_filelist();
cur_loc = wrap(cur_loc + dir); cur_loc = wrap(cur_loc + dir);
@ -1136,29 +1302,35 @@ void advance(int dir)
// set this file to new value // set this file to new value
fileinfo[cur_loc].lru = ++lru_stamp; fileinfo[cur_loc].lru = ++lru_stamp;
// need to grab the cache flush_cache(FALSE);
// we're mucking with the cache like mad, so grab the mutex; it doubles
// as a dc_shared mutex, so don't release until we're done with dc_shared
stb_mutex_begin(cache_mutex); stb_mutex_begin(cache_mutex);
flush_cache(TRUE);
dc.num_files = 0; dc.num_files = 0;
queue_disk_command(&dc, cur_loc, 1); // first thing to load: this file queue_disk_command(&dc, cur_loc, 1); // first thing to load: this file
if (dir) { 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); // 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) 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) { if (dc.num_files) {
dc_shared = dc; dc_shared = dc;
for (i=0; i < dc.num_files; ++i) for (i=0; i < dc.num_files; ++i)
assert(dc.files[i]->filedata == NULL); assert(dc.files[i]->filedata == NULL);
// wake up the disk thread if needed
stb_sem_release(disk_command_queue); stb_sem_release(disk_command_queue);
} }
stb_mutex_end(cache_mutex); stb_mutex_end(cache_mutex);
// tell loader not to bother with old data // tell disk loader not to bother with older files
for (i=0; i < MAX_CACHED_IMAGES; ++i) for (i=0; i < MAX_CACHED_IMAGES; ++i)
if (cache[i].lru < lru_stamp-1) if (cache[i].lru < lru_stamp-1)
cache[i].bail = 1; cache[i].bail = 1;
} }
// ctrl-O, or initial command if no filename: run
// GetOpenFileName(), load the specified filelist,
//
static char filenamebuffer[4096]; static char filenamebuffer[4096];
void open_file(void) void open_file(void)
{ {
@ -1377,11 +1549,6 @@ void mouse(UINT ev, int x, int y)
#define VK_SLASH 0xbf #define VK_SLASH 0xbf
#endif #endif
int best_lru = 0;
int downsample_cubic = 0;
int upsample_cubic = 1;
int WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) int WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{ {
@ -1427,6 +1594,7 @@ int WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
} }
} }
if (best) { if (best) {
o(("Post-decode, found a best image, better than any before.\n"));
update_source(best); update_source(best);
} }
flush_cache(FALSE); flush_cache(FALSE);
@ -1540,11 +1708,6 @@ int WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
if (cur) frame(cur); if (cur) frame(cur);
break; break;
case 'C':
upsample_cubic = !upsample_cubic;
downsample_cubic = !downsample_cubic;
break;
case MY_CTRL | VK_OEM_PLUS: case MY_CTRL | VK_OEM_PLUS:
case MY_CTRL | MY_SHIFT | VK_OEM_PLUS: case MY_CTRL | MY_SHIFT | VK_OEM_PLUS:
resize(1); resize(1);
@ -1579,49 +1742,6 @@ int WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
int resize_threads; int resize_threads;
void ideal_window_size(int w, int h, int *w_ideal, int *h_ideal, int *x, int *y)
{
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
*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);
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
compute_size(*w_ideal, *h_ideal, w,h, &w,&h);
w += FRAME*2;
h += FRAME*2;
}
if ((cx != cx2 || cy != cy2) && w <= cx2+FRAME*2 && h <= cy2+FRAME*2) {
*x = (cx2 - w) >> 1;
*y = (cy2 - h) >> 1;
} else {
*x = (cx - w) >> 1;
*y = (cy - h) >> 1;
}
}
int cur_is_current(void) int cur_is_current(void)
{ {
if (!cur_filename) return FALSE; if (!cur_filename) return FALSE;
@ -1648,7 +1768,6 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
resize_threads = stb_processor_count(); resize_threads = stb_processor_count();
resize_threads = 8;
if (resize_threads > MAX_RESIZE) resize_threads = MAX_RESIZE; if (resize_threads > MAX_RESIZE) resize_threads = MAX_RESIZE;
#ifdef _DEBUG #ifdef _DEBUG
@ -1954,7 +2073,7 @@ void *image_resize_work(ImageProcess *q)
return NULL; return NULL;
} }
void image_resize_old(Image *dest, Image *src) void image_resize_bilinear(Image *dest, Image *src)
{ {
ImageProcess proc_buffer[16], *q = stb_temp(proc_buffer, resize_threads * sizeof(*q)); ImageProcess proc_buffer[16], *q = stb_temp(proc_buffer, resize_threads * sizeof(*q));
SplitPoint *p = stb_temp(point_buffer, dest->x * sizeof(*p)); SplitPoint *p = stb_temp(point_buffer, dest->x * sizeof(*p));
@ -2058,13 +2177,14 @@ static Color lerp(Color dest, Color src, uint8 a)
MMX int16 three[4] = { 3,3,3,3 }; MMX int16 three[4] = { 3,3,3,3 };
static void cubicRGBA(uint32 *dest, uint32 *x0, uint32 *x1, uint32 *x2, uint32 *x3, int lerp8, int step_dest, int step_src, int len) static void cubic_interpolate_span(uint32 *dest,
//static uint32 cubicRGBA(uint32 x0, uint32 x1, uint32 x2, uint32 x3, int lerp8) uint32 *x0, uint32 *x1, uint32 *x2, uint32 *x3,
int lerp8, int step_dest, int step_src, int len)
{ {
if (len <= 0) return; if (len <= 0) return;
__asm { __asm {
// these save/restores shouldn't be necessary... but they seem to be in VC6 opt builds // these save/restores shouldn't be necessary... but they seem to be needed
// buggy compiler, or I'm doing something wrong // in VC6 opt builds; either a buggy compiler, or I'm doing something wrong
push eax push eax
push ebx push ebx
push ecx push ecx
@ -2179,7 +2299,7 @@ static int cubic(int x0, int x1, int x2, int x3, int lerp8)
return res; return res;
} }
static void cubicRGBA(Color *dest, Color *x0, Color *x1, Color *x2, Color *x3, int lerp8, int step_dest, int step_src, int len) 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)
{ {
int i; int i;
for (i=0; i < len; ++i) { for (i=0; i < len; ++i) {
@ -2209,7 +2329,7 @@ struct
} cubic_work; } cubic_work;
#define CUBIC_BLOCK 32 #define CUBIC_BLOCK 32
void * grCubicScaleBitmapX_work(int n) void * cubic_interp_1d_x_work(int n)
{ {
int out_w = cubic_work.out_len; int out_w = cubic_work.out_len;
int x,dx,i,j,k,k_start, k_end; int x,dx,i,j,k,k_start, k_end;
@ -2227,7 +2347,7 @@ void * grCubicScaleBitmapX_work(int n)
int xp = (x >> 16); int xp = (x >> 16);
int xw = (x >> 8) & 255; int xw = (x >> 8) & 255;
if (xp == 0) { if (xp == 0) {
cubicRGBA(dest, data+xp,data+xp,data+xp+1,data+xp+2,xw,out->stride,src->stride,k2-k); cubic_interpolate_span(dest, data+xp,data+xp,data+xp+1,data+xp+2,xw,out->stride,src->stride,k2-k);
} else if (xp >= src->x - 2) { } else if (xp >= src->x - 2) {
if (xp == src->x-1) { if (xp == src->x-1) {
for (j=k; j < k2; ++j) { for (j=k; j < k2; ++j) {
@ -2236,10 +2356,10 @@ void * grCubicScaleBitmapX_work(int n)
dest = PLUS(dest , out->stride); dest = PLUS(dest , out->stride);
} }
} else { } else {
cubicRGBA(dest, data+xp-1,data+xp,data+xp+1,data+xp+1,xw,out->stride,src->stride,k2-k); cubic_interpolate_span(dest, data+xp-1,data+xp,data+xp+1,data+xp+1,xw,out->stride,src->stride,k2-k);
} }
} else { } else {
cubicRGBA(dest, data+xp-1,data+xp,data+xp+1,data+xp+2,xw,out->stride,src->stride,k2-k); cubic_interpolate_span(dest, data+xp-1,data+xp,data+xp+1,data+xp+2,xw,out->stride,src->stride,k2-k);
} }
x += dx; x += dx;
} }
@ -2248,7 +2368,7 @@ void * grCubicScaleBitmapX_work(int n)
return NULL; return NULL;
} }
Image *grCubicScaleBitmapX(Image *src, int out_w) Image *cubic_interp_1d_x(Image *src, int out_w)
{ {
int i; int i;
cubic_work.out = bmp_alloc(out_w, src->y); cubic_work.out = bmp_alloc(out_w, src->y);
@ -2258,15 +2378,15 @@ Image *grCubicScaleBitmapX(Image *src, int out_w)
barrier(); barrier();
if (resize_threads == 1) { if (resize_threads == 1) {
grCubicScaleBitmapX_work(0); cubic_interp_1d_x_work(0);
} else { } else {
volatile void *which[MAX_RESIZE]; volatile void *which[MAX_RESIZE];
for (i=0; i < resize_threads; ++i) for (i=0; i < resize_threads; ++i)
which[i] = (void *) 1; which[i] = (void *) 1;
barrier(); barrier();
for (i=1; i < resize_threads; ++i) for (i=1; i < resize_threads; ++i)
stb_workq(resize_workers, (stb_thread_func) grCubicScaleBitmapX_work, (void *) i, which+i); stb_workq(resize_workers, (stb_thread_func) cubic_interp_1d_x_work, (void *) i, which+i);
grCubicScaleBitmapX_work(0); cubic_interp_1d_x_work(0);
for(;;) { for(;;) {
for (i=1; i < resize_threads; ++i) for (i=1; i < resize_threads; ++i)
@ -2279,7 +2399,7 @@ Image *grCubicScaleBitmapX(Image *src, int out_w)
return cubic_work.out; return cubic_work.out;
} }
Image *grCubicScaleBitmapY_work(int n) Image *cubic_interp_1d_y_work(int n)
{ {
int y,dy,j,j_end; int y,dy,j,j_end;
int out_h = cubic_work.out_len; int out_h = cubic_work.out_len;
@ -2298,12 +2418,12 @@ Image *grCubicScaleBitmapY_work(int n)
uint32 *data2 = PLUS(data1,src->stride); uint32 *data2 = PLUS(data1,src->stride);
uint32 *data0 = (yp > 0) ? PLUS(data1, - src->stride) : data1; uint32 *data0 = (yp > 0) ? PLUS(data1, - src->stride) : data1;
uint32 *data3 = (yp < src->y-2) ? PLUS(data2, src->stride) : data2; uint32 *data3 = (yp < src->y-2) ? PLUS(data2, src->stride) : data2;
cubicRGBA(dest, data0, data1, data2, data3, yw, 4,4,out->x); cubic_interpolate_span(dest, data0, data1, data2, data3, yw, 4,4,out->x);
} }
return NULL; return NULL;
} }
Image *grCubicScaleBitmapY(Image *src, int out_h) Image *cubic_interp_1d_y(Image *src, int out_h)
{ {
int i; int i;
cubic_work.src = src; cubic_work.src = src;
@ -2313,15 +2433,15 @@ Image *grCubicScaleBitmapY(Image *src, int out_h)
barrier(); barrier();
if (resize_threads == 1) { if (resize_threads == 1) {
grCubicScaleBitmapY_work(0); cubic_interp_1d_y_work(0);
} else { } else {
volatile void *which[MAX_RESIZE]; volatile void *which[MAX_RESIZE];
for (i=0; i < resize_threads; ++i) for (i=0; i < resize_threads; ++i)
which[i] = (void *) 1; which[i] = (void *) 1;
barrier(); barrier();
for (i=1; i < resize_threads; ++i) for (i=1; i < resize_threads; ++i)
stb_workq(resize_workers, (stb_thread_func) grCubicScaleBitmapY_work, (void *) i, which+i); stb_workq(resize_workers, (stb_thread_func) cubic_interp_1d_y_work, (void *) i, which+i);
grCubicScaleBitmapY_work(0); cubic_interp_1d_y_work(0);
for(;;) { for(;;) {
for (i=1; i < resize_threads; ++i) for (i=1; i < resize_threads; ++i)
@ -2335,7 +2455,7 @@ Image *grCubicScaleBitmapY(Image *src, int out_h)
} }
// downsampling // downsampling
Image *grScaleBitmapOneHalf(Image *src) Image *downsample_half(Image *src)
{ {
int i,j, w,h; int i,j, w,h;
Image *res; Image *res;
@ -2363,7 +2483,7 @@ Image *grScaleBitmapOneHalf(Image *src)
return res; return res;
} }
Image *grScaleBitmapTwoThirds(Image *src) Image *downsample_two_thirds(Image *src)
{ {
int i,j, w,h; int i,j, w,h;
Image *res; Image *res;
@ -2420,13 +2540,13 @@ Image *grScaleBitmap(Image *src, int gx, int gy, Image *dest)
// maybe should do something smarter here, like find the // maybe should do something smarter here, like find the
// nearest box size, instead of repetitive powers of two // nearest box size, instead of repetitive powers of two
while (gx <= (src->x >> 1) && gy <= (src->y >> 1)) { while (gx <= (src->x >> 1) && gy <= (src->y >> 1)) {
src = grScaleBitmapOneHalf(src); src = downsample_half(src);
if (to_free) imfree(to_free); if (to_free) imfree(to_free);
to_free = src; to_free = src;
} }
if (gx < src->x * 0.666666f && gy < src->y * 0.666666f) { if (gx < src->x * 0.666666f && gy < src->y * 0.666666f) {
src = grScaleBitmapTwoThirds(src); src = downsample_two_thirds(src);
if (to_free) imfree(to_free); if (to_free) imfree(to_free);
to_free = src; to_free = src;
} }
@ -2441,14 +2561,14 @@ Image *grScaleBitmap(Image *src, int gx, int gy, Image *dest)
return res; return res;
} }
} else if (upsample ? upsample_cubic : downsample_cubic) { } else if (upsample ? upsample_cubic : downsample_cubic) {
res = grCubicScaleBitmapY(src, gy); res = cubic_interp_1d_y(src, gy);
if (to_free) imfree(to_free); if (to_free) imfree(to_free);
to_free = res; to_free = res;
res = grCubicScaleBitmapX(res, gx); res = cubic_interp_1d_x(res, gx);
imfree(to_free); imfree(to_free);
} else { } else {
#if 1 #if 1
image_resize_old(dest, src); image_resize_bilinear(dest, src);
if (to_free) imfree(to_free); if (to_free) imfree(to_free);
res = NULL; res = NULL;
#else #else
@ -2466,7 +2586,7 @@ Image *grScaleBitmap(Image *src, int gx, int gy, Image *dest)
void image_resize(Image *dest, Image *src) void image_resize(Image *dest, Image *src)
{ {
#if BPP==3 #if BPP==3
image_resize_old(dest, src); image_resize_bilinear(dest, src);
#else #else
int j; int j;
Image *temp; Image *temp;

View File

@ -1,7 +1,8 @@
Version 0.57 Version 0.57
* feature: cubic image resampling * 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: occasional error when advancing to image that was about to be decoded
* bugfix: comment code * bugfix: commented about 75% of code
* bugfix: fix logic for fitting large images onscreen to not stop a few pixels short * bugfix: fix logic for fitting large images onscreen to not stop a few pixels short
Version 0.56 Version 0.56

View File

@ -19,6 +19,7 @@ CFG=stb_imv - Win32 Debug
!MESSAGE !MESSAGE
!MESSAGE "stb_imv - Win32 Release" (based on "Win32 (x86) Application") !MESSAGE "stb_imv - Win32 Release" (based on "Win32 (x86) Application")
!MESSAGE "stb_imv - Win32 Debug" (based on "Win32 (x86) Application") !MESSAGE "stb_imv - Win32 Debug" (based on "Win32 (x86) Application")
!MESSAGE "stb_imv - Win32 Debug Opt" (based on "Win32 (x86) Application")
!MESSAGE !MESSAGE
# Begin Project # Begin Project
@ -82,12 +83,41 @@ LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept
!ELSEIF "$(CFG)" == "stb_imv - Win32 Debug Opt"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 0
# PROP BASE Output_Dir "stb_imv___Win32_Debug_Opt"
# PROP BASE Intermediate_Dir "stb_imv___Win32_Debug_Opt"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 0
# PROP Output_Dir "stb_imv___Win32_Debug_Opt"
# PROP Intermediate_Dir "stb_imv___Win32_Debug_Opt"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /G6 /MD /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /FD /c
# SUBTRACT BASE CPP /YX
# ADD CPP /nologo /G6 /MD /W3 /GX /Z7 /O2 /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
# ADD BASE RSC /l 0x409 /d "NDEBUG"
# ADD RSC /l 0x409 /d "NDEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386
!ENDIF !ENDIF
# Begin Target # Begin Target
# Name "stb_imv - Win32 Release" # Name "stb_imv - Win32 Release"
# Name "stb_imv - Win32 Debug" # Name "stb_imv - Win32 Debug"
# Name "stb_imv - Win32 Debug Opt"
# Begin Source File # Begin Source File
SOURCE=..\imv.c SOURCE=..\imv.c
@ -95,7 +125,22 @@ SOURCE=..\imv.c
# Begin Source File # Begin Source File
SOURCE=..\notes.txt SOURCE=..\notes.txt
!IF "$(CFG)" == "stb_imv - Win32 Release"
# PROP Exclude_From_Build 1 # PROP Exclude_From_Build 1
!ELSEIF "$(CFG)" == "stb_imv - Win32 Debug"
# PROP Exclude_From_Build 1
!ELSEIF "$(CFG)" == "stb_imv - Win32 Debug Opt"
# PROP BASE Exclude_From_Build 1
# PROP Exclude_From_Build 1
!ENDIF
# End Source File # End Source File
# Begin Source File # Begin Source File
@ -104,7 +149,22 @@ SOURCE=..\stb.h
# Begin Source File # Begin Source File
SOURCE=..\version.bat SOURCE=..\version.bat
!IF "$(CFG)" == "stb_imv - Win32 Release"
# PROP Exclude_From_Build 1 # PROP Exclude_From_Build 1
!ELSEIF "$(CFG)" == "stb_imv - Win32 Debug"
# PROP Exclude_From_Build 1
!ELSEIF "$(CFG)" == "stb_imv - Win32 Debug Opt"
# PROP BASE Exclude_From_Build 1
# PROP Exclude_From_Build 1
!ENDIF
# End Source File # End Source File
# End Target # End Target
# End Project # End Project