From d74b1e7ece922238ab93bd26162cd9be91685682 Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Sat, 7 Nov 2009 17:11:38 -0800 Subject: [PATCH] Add ability to set a background image. I import a png with libpng, load it as a texture, and show it instead of a flat-color background. Includes user interface to specify scale and translation of image, but the rotation is always aligned to the view. [git-p4: depot-paths = "//depot/solvespace/": change = 2071] --- draw.cpp | 51 ++++++++++++++++++++- mouse.cpp | 11 +++++ solvespace.h | 9 ++++ style.cpp | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++ ui.h | 4 ++ wishlist.txt | 1 - 6 files changed, 196 insertions(+), 2 deletions(-) diff --git a/draw.cpp b/draw.cpp index 95d94f49..661c7e66 100644 --- a/draw.cpp +++ b/draw.cpp @@ -479,9 +479,58 @@ void GraphicsWindow::Paint(int w, int h) { ForceTextWindowShown(); } - glClearDepth(1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + if(SS.bgImage.fromFile) { + // If a background image is loaded, then we draw it now as a texture. + // This handles the resizing for us nicely. + glBindTexture(GL_TEXTURE_2D, 10); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, + SS.bgImage.rw, SS.bgImage.rh, + 0, + GL_RGB, GL_UNSIGNED_BYTE, + SS.bgImage.fromFile); + + double tw = ((double)SS.bgImage.w) / SS.bgImage.rw, + th = ((double)SS.bgImage.h) / SS.bgImage.rh; + + double mmw = SS.bgImage.w / SS.bgImage.scale, + mmh = SS.bgImage.h / SS.bgImage.scale; + + Vector origin = SS.bgImage.origin; + origin = origin.DotInToCsys(projRight, projUp, n); + // Place the depth of our origin at the point that corresponds to + // w = 1, so that it's unaffected by perspective. + origin.z = (offset.ScaledBy(-1)).Dot(n); + origin = origin.ScaleOutOfCsys(projRight, projUp, n); + + glEnable(GL_TEXTURE_2D); + glBegin(GL_QUADS); + glTexCoord2d(0, 0); + glxVertex3v(origin); + + glTexCoord2d(0, th); + glxVertex3v(origin.Plus(projUp.ScaledBy(mmh))); + + glTexCoord2d(tw, th); + glxVertex3v(origin.Plus(projRight.ScaledBy(mmw).Plus( + projUp. ScaledBy(mmh)))); + + glTexCoord2d(tw, 0); + glxVertex3v(origin.Plus(projRight.ScaledBy(mmw))); + glEnd(); + glDisable(GL_TEXTURE_2D); + } + + // Now clear the depth; so the background color and image are both at + // the very back of everything. + glClearDepth(1.0); + // Nasty case when we're reloading the imported files; could be that // we get an error, so a dialog pops up, and a message loop starts, and // we have to get called to paint ourselves. If the sketch is screwed diff --git a/mouse.cpp b/mouse.cpp index f253f639..e0453beb 100644 --- a/mouse.cpp +++ b/mouse.cpp @@ -79,6 +79,13 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, if(GraphicsEditControlIsVisible()) return; if(context.active) return; + if(!orig.mouseDown) { + // If someone drags the mouse into our window with the left button + // already depressed, then we don't have our starting point; so + // don't try. + leftDown = false; + } + if(rightDown) { middleDown = true; shiftDown = !shiftDown; @@ -703,6 +710,8 @@ bool GraphicsWindow::ConstrainPointByHovered(hEntity pt) { } void GraphicsWindow::MouseLeftDown(double mx, double my) { + orig.mouseDown = true; + if(GraphicsEditControlIsVisible()) return; HideTextEditControl(); @@ -974,6 +983,8 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { } void GraphicsWindow::MouseLeftUp(double mx, double my) { + orig.mouseDown = false; + switch(pending.operation) { case DRAGGING_POINTS: case DRAGGING_CONSTRAINT: diff --git a/solvespace.h b/solvespace.h index e6d82f36..6046c67d 100644 --- a/solvespace.h +++ b/solvespace.h @@ -669,11 +669,20 @@ public: VectorFileWriter *out); static void MenuAnalyze(int id); + + // Additional display stuff struct { SContour path; hEntity point; } traced; SEdgeList nakedEdges; + struct { + BYTE *fromFile; + int w, h; + int rw, rh; + double scale; // pixels per mm + Vector origin; + } bgImage; void MarkGroupDirty(hGroup hg); void MarkGroupDirtyByEntity(hEntity he); diff --git a/style.cpp b/style.cpp index b54357ad..f3bd5aa0 100644 --- a/style.cpp +++ b/style.cpp @@ -1,4 +1,5 @@ #include "solvespace.h" +#include #define clamp01(x) (max(0, min(1, (x)))) @@ -108,6 +109,8 @@ void Style::LoadFactoryDefaults(void) { s->name.strcpy(CnfPrefixToName(d->cnfPrefix)); } SS.backgroundColor = RGB(0, 0, 0); + if(SS.bgImage.fromFile) MemFree(SS.bgImage.fromFile); + SS.bgImage.fromFile = NULL; } void Style::FreezeDefaultStyles(void) { @@ -327,6 +330,87 @@ void TextWindow::ScreenChangeBackgroundColor(int link, DWORD v) { SS.TW.edit.meaning = EDIT_BACKGROUND_COLOR; } +static int RoundUpToPowerOfTwo(int v) +{ + int i; + for(i = 0; i < 31; i++) { + int vt = (1 << i); + if(vt >= v) { + return vt; + } + } + return 0; +} + +void TextWindow::ScreenBackgroundImage(int link, DWORD v) { + if(SS.bgImage.fromFile) MemFree(SS.bgImage.fromFile); + SS.bgImage.fromFile = NULL; + + if(link == 'l') { + FILE *f = NULL; + png_struct *png_ptr = NULL; + png_info *info_ptr = NULL; + + char importFile[MAX_PATH] = ""; + if(!GetOpenFile(importFile, PNG_EXT, PNG_PATTERN)) goto err; + f = fopen(importFile, "rb"); + if(!f) goto err; + + BYTE header[8]; + fread(header, 1, 8, f); + if(png_sig_cmp(header, 0, 8)) goto err; + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + if(!png_ptr) goto err; + + info_ptr = png_create_info_struct(png_ptr); + if(!info_ptr) goto err; + + if(setjmp(png_jmpbuf(png_ptr))) goto err; + + png_init_io(png_ptr, f); + png_set_sig_bytes(png_ptr, 8); + + png_read_png(png_ptr, info_ptr, + PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_ALPHA, NULL); + + int w = info_ptr->width, + h = info_ptr->height; + BYTE **rows = png_get_rows(png_ptr, info_ptr); + + // Round to next-highest powers of two, since the textures require + // that. And round up to 4, to guarantee DWORD alignment. + int rw = max(4, RoundUpToPowerOfTwo(w)), + rh = max(4, RoundUpToPowerOfTwo(h)); + + SS.bgImage.fromFile = (BYTE *)MemAlloc(rw*rh*3); + for(int i = 0; i < h; i++) { + memcpy(SS.bgImage.fromFile + ((h - 1) - i)*(rw*3), rows[i], w*3); + } + SS.bgImage.w = w; + SS.bgImage.h = h; + SS.bgImage.rw = rw; + SS.bgImage.rh = rh; + SS.bgImage.scale = SS.GW.scale; + SS.bgImage.origin = SS.GW.offset.ScaledBy(-1); + +err: + if(png_ptr) png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + if(f) fclose(f); + } + SS.later.showTW = true; +} + +void TextWindow::ScreenChangeBackgroundImageScale(int link, DWORD v) { + char str[300]; + sprintf(str, "%.3f", + (SS.viewUnits == SolveSpace::UNIT_MM) ? SS.bgImage.scale : + SS.bgImage.scale * 25.4); + SS.TW.edit.meaning = EDIT_BACKGROUND_IMG_SCALE; + ShowTextEditControl(v, 10, str); +} + void TextWindow::ShowListOfStyles(void) { Printf(true, "%Ft color style-name"); @@ -354,6 +438,28 @@ void TextWindow::ShowListOfStyles(void) { REDf(rgb), GREENf(rgb), BLUEf(rgb), top[rows-1] + 2, &ScreenChangeBackgroundColor); + Printf(false, ""); + Printf(false, "%Ft background bitmap image%E"); + if(SS.bgImage.fromFile) { + Printf(false, "%Ba %Ftwidth:%E %dpx %Ftheight:%E %dpx", + SS.bgImage.w, SS.bgImage.h); + if(SS.viewUnits == SolveSpace::UNIT_MM) { + Printf(false, " %Ftscale:%E %# px/mm %Fl%Ll%f%D[change]%E", + SS.bgImage.scale, + &ScreenChangeBackgroundImageScale, top[rows-1] + 2); + } else { + Printf(false, " %Ftscale:%E %# px/inch %Fl%Ll%f%D[change]%E", + SS.bgImage.scale*25.4, + &ScreenChangeBackgroundImageScale, top[rows-1] + 2); + } + Printf(false, "%Ba %Fl%Lc%fclear background image%E", + &ScreenBackgroundImage); + } else { + Printf(false, "%Ba none - %Fl%Ll%fload background image%E", + &ScreenBackgroundImage); + Printf(false, " (bottom left will be center of view)"); + } + Printf(false, ""); Printf(false, " %Fl%Ll%fload factory defaults%E", &ScreenLoadFactoryDefaultStyles); @@ -575,6 +681,22 @@ bool TextWindow::EditControlDoneForStyles(char *str) { } break; + case EDIT_BACKGROUND_IMG_SCALE: { + Expr *e = Expr::From(str); + if(e) { + double ev = e->Eval(); + if(ev < 0.001 || isnan(ev)) { + Error("Scale must not be zero or negative!"); + } else { + SS.bgImage.scale = + (SS.viewUnits == SolveSpace::UNIT_MM) ? ev : + ev / 25.4; + } + } else { + Error("Not a valid number or expression: '%s'", str); + } + break; + } default: return false; } return true; diff --git a/ui.h b/ui.h index 74327fd4..e652e8a2 100644 --- a/ui.h +++ b/ui.h @@ -101,6 +101,7 @@ public: static const int EDIT_STYLE_FILL_COLOR = 54; static const int EDIT_STYLE_NAME = 55; static const int EDIT_BACKGROUND_COLOR = 56; + static const int EDIT_BACKGROUND_IMG_SCALE = 57; struct { int meaning; int i; @@ -160,6 +161,7 @@ public: static void ScreenCreateCustomStyle(int link, DWORD v); static void ScreenLoadFactoryDefaultStyles(int link, DWORD v); static void ScreenAssignSelectionToStyle(int link, DWORD v); + static void ScreenBackgroundImage(int link, DWORD v); static void ScreenShowConfiguration(int link, DWORD v); static void ScreenGoToWebsite(int link, DWORD v); @@ -195,6 +197,7 @@ public: static void ScreenChangeStyleTextAngle(int link, DWORD v); static void ScreenChangeStyleColor(int link, DWORD v); static void ScreenChangeBackgroundColor(int link, DWORD v); + static void ScreenChangeBackgroundImageScale(int link, DWORD v); bool EditControlDoneForStyles(char *s); bool EditControlDoneForConfiguration(char *s); @@ -322,6 +325,7 @@ public: Vector projUp; double scale; struct { + bool mouseDown; Vector offset; Vector projRight; Vector projUp; diff --git a/wishlist.txt b/wishlist.txt index c3b8398e..d7408e6f 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,6 +1,5 @@ copy and paste -background image associative entities from solid model, as a special group n*log(n) intersection finding