// Set section alignment to be nice and small #pragma comment(linker, "/FILEALIGN:0x200") //#pragma comment(linker, "/OPT:NOWIN98") #define WIN32_MEAN_AND_LEAN #define WIN32_EXTRA_LEAN #include #include #include #include #include #define STB_DEFINE #include "stb.h" /* http://nothings.org/stb.h */ #include "stb_image.c" /* http://nothings.org/stb_image.c */ void error(char *str) { MessageBox(NULL, str, "imv(stb) error", MB_OK); } void o(char *str, ...) { #ifdef _DEBUG char buffer[1024]; va_list va; va_start(va,str); vsprintf(buffer, str, va); va_end(va); OutputDebugString(buffer); #endif } #define FRAME 3 #define FRAME2 (FRAME >> 1) #define GREY 192 enum { WM_APP_DECODED = WM_APP, }; #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 szAppName[]="stb_imv"; HDC hDC; HWND win; HGLRC hRC; HINSTANCE hInst; char *displayName = "imv(stb)"; #define BPP 4 void platformDrawBitmap(HDC hdc, int x, int y, unsigned char *bits, int w, int h, int stride) { 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 if (IsBadReadPtr(bits, w*h*BPP)) { assert(0); } 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(); } } void barrier(void) { long dummy; __asm { xchg dummy, eax } } typedef struct { int x,y,stride,frame; unsigned char *pixels; } Image; enum { // owned by main thread LOAD_unused=0, // empty slot LOAD_inactive, // filename slot, not loaded // finished reading, needs decoding--originally decoder // owned this, but then we couldn't free from the cache LOAD_reading_done, // in any of the following states, the image is as done as it can be LOAD_error_reading, LOAD_error_decoding, LOAD_available, // loaded successfully // owned by resizer LOAD_resizing, // owned by loader LOAD_reading, // owned by decoder LOAD_decoding, }; #define MAIN_OWNS(x) ((x)->status <= LOAD_available) typedef struct { int priority; char *filename; char *filedata; int len; Image *image; char *error; int status; int bail; int lru; } ImageFile; stb_semaphore cache_mutex; stb_semaphore decode_queue, decode_mutex; stb_semaphore disk_command_queue; typedef struct { int num_files; ImageFile *files[4]; } DiskCommand; volatile DiskCommand dc_shared; // awake the main thread when something interesting happens void wake(int message) { PostMessage(win, message, 0,0); } volatile ImageFile *image_decode_pending; void *diskload_task(void *p) { for(;;) { int i; DiskCommand dc; // wait for a command from the main thread o("READ: Waiting for disk request.\n"); stb_sem_waitfor(disk_command_queue); // grab the command; don't let the command or the cache change while we do it stb_sem_waitfor(cache_mutex); { dc = dc_shared; for (i=0; i < dc.num_files; ++i) dc.files[i]->status = LOAD_reading; } stb_sem_release(cache_mutex); o("READ: Got disk request, %d items.\n", dc.num_files); 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 if (dc.files[i]->bail) { o("READ: Bailing on disk request\n"); dc.files[i]->status = LOAD_inactive; } else { o("READ: Loading file %s\n", dc.files[i]->filename); data = stb_file(dc.files[i]->filename, &n); // don't need to mutex these, because we own them via ->status if (data == NULL) { dc.files[i]->error = strdup("can't open"); dc.files[i]->filedata = NULL; dc.files[i]->len = 0; barrier(); dc.files[i]->status = LOAD_error_reading; } else { dc.files[i]->error = NULL; assert(dc.files[i]->filedata == NULL); dc.files[i]->filedata = data; dc.files[i]->len = n; barrier(); dc.files[i]->status = LOAD_reading_done; // now set the image decode command for what we just loaded // wake the main thread? not needed, it has nothing to do // wake(WM_APP_LOADED); stb_sem_release(decode_queue); } } } o("READ: Finished command\n"); } } #define MAX_CACHED_IMAGES 200 volatile ImageFile cache[MAX_CACHED_IMAGES]; void make_image(Image *z, int image_x, int image_y, uint8 *image_data, int image_n); volatile int decoder_idle = TRUE; volatile ImageFile *decoder_choose(void) { int i, best_lru=0; volatile ImageFile *best = NULL; start: 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; } } } if (best) { int retry = FALSE; stb_sem_waitfor(cache_mutex); if (best->status == LOAD_reading_done) best->status = LOAD_decoding; else retry = TRUE; stb_sem_release(cache_mutex); if (retry) goto start; } return best; } void *decode_task(void *p) { for(;;) { volatile ImageFile *f; f = decoder_choose(); if (f == NULL) { o("DECODE: blocking\n"); stb_sem_waitfor(decode_queue); o("DECODE: woken\n"); } else { int x,y,n; uint8 *data; assert(f->status == LOAD_decoding); o("DECIDE: decoding %s\n", f->filename); data = stbi_load_from_memory(f->filedata, f->len, &x, &y, &n, BPP); decoder_idle = TRUE; o("DECODE: decoded %s\n", f->filename); free(f->filedata); f->filedata = NULL; if (data == NULL) { f->error = strdup(stbi_failure_reason()); barrier(); f->status = LOAD_error_reading; } else { f->image = (Image *) malloc(sizeof(*f->image)); make_image(f->image, x,y,data,n); barrier(); f->status = LOAD_available; // wake up the main thread in case this new data is useful wake(WM_APP_DECODED); } } } } Image *source; ImageFile *source_c; 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; if (i->pixels == NULL) { free(i); return NULL; } return i; } void make_image(Image *z, int image_x, int image_y, uint8 *image_data, int image_n) { int i; z->pixels = image_data; z->x = image_x; z->y = image_y; z->stride = image_x*BPP; z->frame = 0; // swap RGB to BGR for (i=0; i < image_x*image_y*BPP; i += BPP) { unsigned char t = image_data[i+0]; image_data[i+0] = image_data[i+2]; image_data[i+2] = t; #if BPP==4 if (image_n == 4) { // apply alpha unsigned char *p = image_data+i; int a = (255-p[3]); p[0] += (((200 - (int) p[0])*a)>>8); p[1] += (((100 - (int) p[1])*a)>>8); p[2] += (((200 - (int) p[2])*a)>>8); } #endif } } 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); #ifdef GREY 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); #endif 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); } #ifdef GREY 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); } #endif } void imfree(Image *x) { free(x->pixels); free(x); } 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; } static void image_resize(Image *dest, Image *src, ImageFile *cache); Image *cur; void display(HWND win, HDC hdc) { RECT rect,r2; HBRUSH b = GetStockObject(BLACK_BRUSH); int w,h,x,y; GetClientRect(win, &rect); w = rect.right - rect.left; h = rect.bottom - rect.top; x = (w - cur->x) >> 1; y = (h - cur->y) >> 1; platformDrawBitmap(hdc, x,y,cur->pixels, cur->x, cur->y, cur->stride); // draw in the borders 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); } typedef struct { int x,y; int w,h; } queued_size; queued_size qs; struct { queued_size size; Image *image; } pending_resize; typedef struct { ImageFile *src; Image dest; Image *result; } Resize; Resize res; void * work_resize(void *p) { Resize *r = (Resize *) p; image_resize(&r->dest, r->src->image, r->src); return r->result; } stb_workqueue *resize_workers; void compute_size(int gw, int gh, int sw, int sh, int *ox, int *oy) { gw -= FRAME*2; gh -= FRAME*2; sw -= FRAME*2; sh -= FRAME*2; if (gw*sh > gh*sw) { *oy = gh; *ox = gh * sw/sh; } else { *ox = gw; *oy = gw * sh/sw; } } void queue_resize(int w, int h, ImageFile *src_c, int immediate) { Image *src = src_c->image; Image *dest; int w2,h2; if (!immediate) assert(pending_resize.size.w); // create (w2,h2) matching aspect ratio of w/h w -= FRAME*2; h -= FRAME*2; if (w*src->y > h*src->x) { h2 = h; w2 = h2 * src->x / src->y; } else { w2 = w; h2 = w2 * src->y / src->x; } assert(w2 >= 0 && h2 >= 0); dest = bmp_alloc(w2+FRAME*2,h2+FRAME*2); assert(dest); frame(dest); res.src = src_c; res.dest = image_region(dest, FRAME, FRAME, w2, h2); res.result = dest; if (!immediate) { src_c->status = LOAD_resizing; pending_resize.image = NULL; stb_workq(resize_workers, work_resize, &res, &pending_resize.image); } else { image_resize(&res.dest, src, NULL); pending_resize.image = res.result; res.result = NULL; } } void enqueue_resize(int left, int top, int width, int height) { if ((width == cur->x && height >= cur->y) || (height == cur->y && width >= cur->x)) { // no resize necessary, just a variant of the current shape MoveWindow(win, left, top, width, height, TRUE); InvalidateRect(win, NULL, FALSE); } else { qs.x = left; qs.y = top; qs.w = width; qs.h = height; } } void ideal_window_size(int w, int h, int *w_ideal, int *h_ideal, int *x, int *y); void update_source(ImageFile *q) { Image *z = q->image; int w2,h2; int w,h,x,y; source = z; source_c = q; w2 = source->x+FRAME*2, h2 = source->y+FRAME*2; ideal_window_size(w2,h2, &w,&h, &x,&y); if (w == source->x+FRAME*2 && h == source->y+FRAME*2) { int j; unsigned char *p = z->pixels; imfree(cur); cur = bmp_alloc(z->x + FRAME*2, z->y + FRAME*2); frame(cur); { for (j=0; j < z->y; ++j) { unsigned char *q = cur->pixels + (j+FRAME)*cur->stride + FRAME*BPP; memcpy(q, p, z->x*BPP); p += z->x*BPP; } } MoveWindow(win, x,y,w,h, TRUE); InvalidateRect(win, NULL, FALSE); } else { qs.x = x; qs.y = y; qs.w = w; qs.h = h; } } char path_to_file[4096], *filename; char **image_files; int cur_loc = -1; struct { char *filename; int lru; } *fileinfo; stb_sdict *file_cache; void init_filelist(void) { char *s = NULL; int i; if (fileinfo) { filename = s = strdup(fileinfo[cur_loc].filename); for (i=0; i < stb_arr_len(fileinfo); ++i) { free(fileinfo[i].filename); } stb_arr_free(fileinfo); } image_files = stb_readdir_files_mask(path_to_file, "*.jpg;*.jpeg;*.png;*.bmp"); if (image_files == NULL) error("Error: couldn't read directory."); stb_fixpath(filename); stb_create_thread(diskload_task, NULL); stb_create_thread(decode_task, NULL); cur_loc = 0; stb_arr_setlen(fileinfo, stb_arr_len(image_files)); 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; } if (s) free(s); } int lru_stamp=1; int max_cache_bytes = 256 * (1 << 20); // 256 MB; one 5MP image is 20MB #define MIN_CACHE 3 // always keep 3 images cached, to allow prefetching 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); } void flush_cache(int locked) { int limit; volatile ImageFile *list[MAX_CACHED_IMAGES]; int i, total=0, occupied_slots=0, n=0; 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; } } limit = MAX_CACHED_IMAGES - MIN_CACHE; if (total > max_cache_bytes || occupied_slots > limit) { qsort((void *) list, n, sizeof(*list), ImageFilePtrCompare); if (!locked) stb_sem_waitfor(cache_mutex); for (i=0; i < n && occupied_slots > MIN_CACHE && (occupied_slots > limit || total > max_cache_bytes); ++i) { ImageFile p; /* @TODO: this is totally squirrely and probably buggy. we need to * rethink how we can propose things to the disk loader and then * 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]->filename = NULL; list[i]->filedata = NULL; list[i]->len = 0; list[i]->image = NULL; list[i]->error = NULL; list[i]->status = LOAD_unused; } } if (MAIN_OWNS(&p) && p.status != LOAD_unused) { if (!locked) stb_sem_release(cache_mutex); 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); if (!locked) stb_sem_waitfor(cache_mutex); } } if (!locked) stb_sem_release(cache_mutex); o("Reduced to %d megabytes\n", total >> 20); } } int wrap(int z) { int n = stb_arr_len(image_files); if (z < 0) return z + n; while (z >= n) z = z - n; return z; } void queue_disk_command(DiskCommand *dc, int which, int make_current) { 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; } if (z->status == LOAD_available) { if (make_current) update_source((ImageFile *) z); return; } if (z->status != LOAD_inactive) { // there was an error loading it... @todo, display the error return; } // it's a go, use z } else { int i,tried_again=FALSE; // find a cache slot try_again: for (i=0; i < MAX_CACHED_IMAGES; ++i) if (cache[i].status == LOAD_unused) break; if (i == MAX_CACHED_IMAGES) { if (tried_again) { stb_fatal("Internal logic error: no free cache slots, but flush_cache() should free a few"); } tried_again = TRUE; flush_cache(TRUE); goto try_again; } z = &cache[i]; free(z->filename); z->filename = strdup(filename); z->lru = 0; z->status = LOAD_inactive; stb_sdict_add(file_cache, filename, (void *) z); } assert(z->status == LOAD_inactive); z->status = LOAD_inactive; z->image = NULL; z->bail = 0; z->lru = fileinfo[which].lru; dc->files[dc->num_files++] = (ImageFile *) z; } void advance(int dir) { DiskCommand dc; int i; if (image_files == NULL) 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; // need to grab the cache stb_sem_waitfor(cache_mutex); dc.num_files = 0; queue_disk_command(&dc, cur_loc, 1); // first thing to load: this file 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) if (dc.num_files) { dc_shared = dc; stb_sem_release(disk_command_queue); } stb_sem_release(cache_mutex); // tell loader not to bother with old data for (i=0; i < MAX_CACHED_IMAGES; ++i) if (cache[i].lru < lru_stamp-1) cache[i].bail = 1; } #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; 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; } } // 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); { RECT rect; GetWindowRect(win, &rect); x = (rect.left + rect.right)>>1; y = (rect.top + rect.bottom)>>1; x -= x2>>1; y -= y2>>1; enqueue_resize(x,y,x2,y2); } } enum { MODE_none, MODE_drag, MODE_resize, } dragmode; #define setmode(x) (dragmode = x) #define ismode(x) (dragmode == x) #define anymode() !ismode(MODE_none) static int ex,ey; // original mousedown location for snapping to that static int ex2,ey2; // original mousedown location relative to bottom right 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; } if (h2 < 12) { h2 = h >> 2; if (h2 < 4) h2 = h >> 1; } 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) { 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); GetWindowRect(win, &rect); 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; GetWindowRect(win, &rect); assert(rx || ry); #define LIMIT 16 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 (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; } } #ifndef VK_OEM_PLUS #define VK_OEM_PLUS 0xbb #define VK_OEM_MINUS 0xbd #endif int best_lru = 0; int WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CREATE: { win = hWnd; break; } 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) { update_source(best); } flush_cache(FALSE); break; } case WM_MOUSEMOVE: case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_LBUTTONUP: case WM_RBUTTONUP: 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; hDC = BeginPaint(hWnd, &ps); 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; 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; 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_ALT | '\r': // alt-enter 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; 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) { *w_ideal = w; *h_ideal = h; } else if (w - FRAME*2 <= cx2 && h - FRAME*2 <= cy2) { *w_ideal = w; *h_ideal = h; } else { // will we show more if we use the full desktop? int w1,h1,w2,h2; compute_size(cx ,cy ,w,h,&w1,&h1); compute_size(cx2,cy2,w,h,&w2,&h2); if (h1 > h2*1.25 || w1 > w2*1.25) { *w_ideal = stb_min(cx,w1); *h_ideal = stb_min(cy,h1); } else { *w_ideal = stb_min(cx2,w2); *h_ideal = stb_min(cy2,h2); } // compute actual size image will be compute_size(*w_ideal, *h_ideal, w,h, &w,&h); } 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 WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { int argc; char **argv = stb_tokens_quoted(lpCmdLine, " ", &argc); MEMORYSTATUS mem; MSG msg; WNDCLASSEX wndclass; HWND hWnd; int physmem; int image_x, image_y; unsigned char *image_data; int image_n; resize_threads = stb_processor_count(); hInst = hInstance; GlobalMemoryStatus(&mem); physmem = mem.dwTotalPhys; /* Register the frame class */ memset(&wndclass, 0, sizeof(wndclass)); wndclass.cbSize = sizeof(wndclass); wndclass.style = CS_OWNDC; 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; if (argc < 1) { MessageBox(NULL, "Specify an image file to view", "imv(stb)", MB_OK); exit(0); } resize_workers = stb_workq_new(resize_threads, resize_threads * 4); cache_mutex = stb_sem_new(1,0); disk_command_queue = stb_sem_new(1,1); decode_queue = stb_sem_new(1,1); decode_mutex = stb_sem_new(1,0); image_data = stbi_load(argv[0], &image_x, &image_y, &image_n, BPP); if (image_data == NULL) { char *why = stbi_failure_reason(); char buffer[512]; sprintf(buffer, "'%s': %s", argv[0], why); error(buffer); exit(0); } stb_fixpath(argv[0]); stb_splitpath(path_to_file, argv[0], STB_PATH); filename = argv[0]; 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]); { 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) { 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); cur = pending_resize.image; pending_resize.image = NULL; } 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) { if ((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); } 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 { o("Finished resize\n"); imfree(cur); cur = pending_resize.image; 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; display(hWnd, hDC); } 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; ImageFile *src_c; 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; } volatile reentry; void image_resize(Image *dest, Image *src, ImageFile *src_c) { 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(reentry == 0); assert(src->frame == 0); ++reentry; 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; q[i].src_c = src_c; 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); } } if(src_c) src_c->status = LOAD_available; stb_tempfree(point_buffer, p); stb_tempfree(proc_buffer , q); --reentry; }