diff options
-rw-r--r-- | doc/testing.txt | 44 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 107 | ||||
-rw-r--r-- | tests/FindGLEW.cmake | 3 | ||||
-rw-r--r-- | tests/OffscreenContext.h | 2 | ||||
-rw-r--r-- | tests/OffscreenContext.mm | 10 | ||||
-rw-r--r-- | tests/OffscreenContextGLX.cc (renamed from tests/OffscreenContext.cc) | 63 | ||||
-rw-r--r-- | tests/OffscreenContextWGL.cc | 58 | ||||
-rw-r--r-- | tests/OffscreenView.cc | 137 | ||||
-rw-r--r-- | tests/OffscreenView.h | 4 | ||||
-rw-r--r-- | tests/cgalpngtest.cc | 3 | ||||
-rw-r--r-- | tests/csgtestcore.cc | 197 | ||||
-rw-r--r-- | tests/fbo.cc | 21 | ||||
-rw-r--r-- | tests/system-gl.cc | 39 | ||||
-rw-r--r-- | tests/system-gl.h | 3 | ||||
-rwxr-xr-x | tests/test_cmdline_tool.py | 34 | ||||
-rwxr-xr-x | tests/test_pretty_print.py | 413 | ||||
-rw-r--r-- | tests/yee_compare.cpp | 681 | ||||
-rw-r--r-- | tests/yee_compare.h | 126 |
18 files changed, 903 insertions, 1042 deletions
diff --git a/doc/testing.txt b/doc/testing.txt index 66ceac2..9391832 100644 --- a/doc/testing.txt +++ b/doc/testing.txt @@ -1,7 +1,7 @@ Running regression tests: ------------------------- -Prerequisites: cmake, python +Prerequisites: cmake, python, ImageMagick 6.5.9.3 or newer A) Building test environment @@ -16,25 +16,14 @@ First, get a normal build working by following instructions at http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Building_on_Windows Then, from the QT command prompt: -$ cd tests -$ cmake . -DCMAKE_BUILD_TYPE=Release -$ sed -i s/\/MD/\/MT/ CMakeCache.txt -$ cmake . -$ nmake -f Makefile +> cd tests +> cmake . -DCMAKE_BUILD_TYPE=Release +> sed -i s/\/MD/\/MT/ CMakeCache.txt +> cmake . +> nmake -f Makefile B) Running tests -Easy version: -$ make test - -Windows: -$ nmake -f Makefile test - -Headless servers (no X11): -$ Xvnc :5 -screen 0 800x600x24 & -$ DISPLAY=:5 make test - -Partial or extended test runs: $ ctest Runs tests enabled by default $ ctest -R <regex> Runs only matching tests, e.g. ctest -R dxf $ ctest -C <configs> Adds extended tests belonging to configs. @@ -44,6 +33,9 @@ $ ctest -C <configs> Adds extended tests belonging to configs. Examples - test all examples All - test everything +Headless unix servers (no X11): +$ Xvfb :5 -screen 0 800x600x24 & +$ DISPLAY=:5 ctest Adding a new regression test: ------------------------------ @@ -59,20 +51,16 @@ Adding a new regression test: 7) run the test normally and verify that it passes: $ ctest -R mytest -Troubleshooting a failed test: +Troubleshooting: ------------------------------ -You can run a single test by passing the test name to ctest: - $ ctest -R throwntogethertest_sphere +To helping CMAKE find eigen2, OpenCSG, CGAL, Boost, and GLEW, you can use the +-D option. Here are some examples: -You can run a series of tests by passing part of a name to ctest: - $ ctest -R cgalpng # runs all cgalpng tests - $ ctest -R sphere # runs all sphere tests - + cmake . -DOPENCSG_DIR=~/OpenCSG-1.3.2 + cmake . -DCGAL_DIR=c:\CGAL-3.7 -DBOOST_ROOT=c:\boost_1_46_0 + Logs of test runs are found in tests/build/Testing/Temporary +Pretty-printed html output is in a subdir of tests/build/Testing/Temporary Expected results are found in tests/regression/* Actual results are found in tests/build/testname-output/* - -You can also compile a single test program: - - $ make cgalpngtest diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 29f8b25..314b51f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,11 +27,7 @@ endif() if(WIN32_STATIC_BUILD) if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") - set(EMSG "\nTo build Win32 STATIC OpenSCAD tests you must run") - set(EMSG "${EMSG} \ncmake .. -DCMAKE_BUILD_TYPE=Release") - set(EMSG "${EMSG} \nthen replace /MD with /MT in CMakeCache.txt") - set(EMSG "${EMSG} \ni.e. sed -i s/\\/MD/\\/MT/ CMakeCache.txt") - set(EMSG "${EMSG} \nthen re-run cmake ..") + set(EMSG "\nTo build Win32 STATIC OpenSCAD please see doc/testing.txt") message(FATAL_ERROR ${EMSG}) endif() endif() @@ -75,24 +71,26 @@ if (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") set(BOOST_ROOT "$ENV{MACOSX_DEPLOY_DIR}") endif() -if(BOOST_ROOT) - #set(Boost_DEBUG TRUE) - set(Boost_NO_SYSTEM_PATHS TRUE) - set(Boost_ADDITIONAL_VERSIONS "1.47.0") - find_package( Boost 1.35.0 COMPONENTS thread program_options ) - if(Boost_FOUND) - message(STATUS "Boost includes found: " ${Boost_INCLUDE_DIRS}) - message(STATUS "Boost libraries found:") - foreach(boostlib ${Boost_LIBRARIES}) - message(STATUS " " ${boostlib}) - endforeach() - include_directories(${Boost_INCLUDE_DIRS}) - else() - message(STATUS "BOOST_ROOT:" ${BOOST_ROOT}) - message(FATAL_ERROR "BOOST_ROOT specified but no boost found") - endif() +if (WIN32) + set(Boost_USE_STATIC_LIBS TRUE) + set(BOOST_STATIC TRUE) + set(BOOST_THREAD_USE_LIB TRUE) +endif() + +#set(Boost_DEBUG TRUE) +set(Boost_NO_SYSTEM_PATHS TRUE) +set(Boost_ADDITIONAL_VERSIONS "1.47.0" "1.46.0") +find_package( Boost 1.35.0 COMPONENTS thread program_options ) +if(Boost_FOUND) + message(STATUS "Boost includes found: " ${Boost_INCLUDE_DIRS}) + message(STATUS "Boost libraries found:") + foreach(boostlib ${Boost_LIBRARIES}) + message(STATUS " " ${boostlib}) + endforeach() + include_directories(${Boost_INCLUDE_DIRS}) else() - message(STATUS "BOOST_ROOT unset. Assuming it will be found automatically.") + message(STATUS "BOOST_ROOT: ${BOOST_ROOT}") + message(FATAL_ERROR "Boost not found.") endif() # Mac OS X @@ -141,10 +139,10 @@ if (NOT OPENCSG_INCLUDE_DIR) message(STATUS "OPENCSG_DIR: " ${OPENCSG_DIR}) find_path(OPENCSG_INCLUDE_DIR opencsg.h - PATHS ${OPENCSG_DIR}/include) + HINTS ${OPENCSG_DIR}/include) find_library(OPENCSG_LIBRARY opencsg - PATHS ${OPENCSG_DIR}/lib) + HINTS ${OPENCSG_DIR}/lib) if (NOT OPENCSG_INCLUDE_DIR OR NOT OPENCSG_LIBRARY) message(FATAL_ERROR "OpenCSG not found") else() @@ -156,7 +154,9 @@ include_directories(${OPENCSG_INCLUDE_DIR}) # GLEW -if (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") +if (NOT $ENV{GLEW_DIR} STREQUAL "") + set(GLEW_DIR "$ENV{GLEW_DIR}") +elseif (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") set(GLEW_DIR "$ENV{MACOSX_DEPLOY_DIR}") endif() @@ -185,16 +185,35 @@ BISON_TARGET(OpenSCADparser ../src/parser.y ${CMAKE_CURRENT_BINARY_DIR}/parser_y ADD_FLEX_BISON_DEPENDENCY(OpenSCADlexer OpenSCADparser) set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/parser_yacc.c PROPERTIES LANGUAGE "CXX") -if (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") +# CGAL + +if (NOT $ENV{CGAL_DIR} STREQUAL "") + set(CGAL_DIR "$ENV{CGAL_DIR}") +elseif (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") set(CGAL_DIR "$ENV{MACOSX_DEPLOY_DIR}/lib/CGAL") set(CMAKE_MODULE_PATH "${CGAL_DIR}") endif() +message(STATUS "CGAL_DIR: " ${CGAL_DIR}) find_package(CGAL REQUIRED) +message(STATUS "CGAL config found in " ${CGAL_USE_FILE} ) +foreach(cgal_incdir ${CGAL_INCLUDE_DIRS}) + message(STATUS "CGAL include found in " ${cgal_incdir} ) +endforeach() +message(STATUS "CGAL libraries found in " ${CGAL_LIBRARIES_DIR} ) if("${CGAL_MAJOR_VERSION}.${CGAL_MINOR_VERSION}" VERSION_LESS 3.6) message(FATAL_ERROR "CGAL >= 3.6 required") endif() include_directories(${CGAL_INCLUDE_DIRS}) +# Imagemagick + +find_package(ImageMagick COMPONENTS convert) +if (ImageMagick_convert_FOUND) + message(STATUS "ImageMagick convert executable found: " ${ImageMagick_convert_EXECUTABLE}) +else() + message(FATAL_ERROR "Couldn't find imagemagick 'convert' program") +endif() + # Internal includes include_directories(../src) @@ -239,7 +258,7 @@ set(NOCGAL_SOURCES set(CGAL_SOURCES ${NOCGAL_SOURCES} - ../src/CSGTermEvaluator.cc + ../src/CSGTermEvaluator.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CGALEvaluator.cc @@ -262,13 +281,13 @@ set(COMMON_SOURCES # if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") message(STATUS "Offscreen OpenGL Context - using Apple CGL") - set(OFFSCREEN_CTX_SOURCE "OffscreenContext.mm") + set(OFFSCREEN_CTX_SOURCE "OffscreenContext.mm" CACHE TYPE STRING) elseif(UNIX) message(STATUS "Offscreen OpenGL Context - using Unix GLX") - set(OFFSCREEN_CTX_SOURCE "OffscreenContext.cc") + set(OFFSCREEN_CTX_SOURCE "OffscreenContextGLX.cc" CACHE TYPE STRING) elseif(WIN32) message(STATUS "Offscreen OpenGL Context - using Microsoft WGL") - set(OFFSCREEN_CTX_SOURCE "OffscreenContextWGL.cc") + set(OFFSCREEN_CTX_SOURCE "OffscreenContextWGL.cc" CACHE TYPE STRING) endif() set(OFFSCREEN_SOURCES @@ -287,6 +306,7 @@ target_link_libraries(tests-cgal tests-common) add_library(tests-nocgal STATIC ${NOCGAL_SOURCES}) target_link_libraries(tests-nocgal tests-common) add_library(tests-offscreen STATIC ${OFFSCREEN_SOURCES}) +# set_target_properties(tests-offscreen PROPERTIES COMPILE_FLAGS "-DENABLE_OPENCSG -DENABLE_CGAL ${CGAL_CXX_FLAGS_INIT}") # # echotest @@ -295,11 +315,6 @@ add_executable(echotest echotest.cc) target_link_libraries(echotest tests-nocgal tests-core ${QT_LIBRARIES} ${OPENGL_LIBRARY} ${Boost_LIBRARIES}) # -# Yangli Hector Yee's PerceptualDiff code -# FIXME: Disabled since we use ImageMagick now. Eventually remove this and the files. -# add_executable(yee_compare yee_compare.cpp lodepng.cpp) - -# # dumptest # add_executable(dumptest dumptest.cc) @@ -437,13 +452,30 @@ macro(add_cmdline_test TESTCMD TESTSUFFIX) set(CONFARG CONFIGURATIONS) set(CONFVAL ${FOUNDCONFIGS}) - add_test(NAME ${TEST_FULLNAME} ${CONFARG} ${CONFVAL} COMMAND ${PYTHON_EXECUTABLE} ${tests_SOURCE_DIR}/test_cmdline_tool.py -s ${TESTSUFFIX} ${CMAKE_BINARY_DIR}/${TESTCMD} "${SCADFILE}") + add_test(NAME ${TEST_FULLNAME} ${CONFARG} ${CONFVAL} COMMAND ${PYTHON_EXECUTABLE} ${tests_SOURCE_DIR}/test_cmdline_tool.py -c ${ImageMagick_convert_EXECUTABLE} -s ${TESTSUFFIX} ${CMAKE_BINARY_DIR}/${TESTCMD} "${SCADFILE}") endif() endforeach() endmacro() enable_testing() +# set up custom pretty printing of results + +set(INFOCMD "execute_process(COMMAND ${CMAKE_CURRENT_BINARY_DIR}/opencsgtest --info OUTPUT_FILE sysinfo.txt)") +set(PRETTYCMD "\"${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_pretty_print.py --builddir=${CMAKE_CURRENT_BINARY_DIR}\"") +set(CTEST_CUSTOM_FILE ${CMAKE_CURRENT_BINARY_DIR}/CTestCustom.cmake) +set(CTEST_CUSTOM_TXT "\n + message(\"running 'opencsgtest --info' to generate sysinfo.txt\")\n + ${INFOCMD}\n + set(CTEST_CUSTOM_POST_TEST ${PRETTYCMD})\n +") +file(WRITE ${CTEST_CUSTOM_FILE} ${CTEST_CUSTOM_TXT}) + +#foreach(FILE test_pretty_print.py) +# configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${FILE} +# ${CMAKE_CURRENT_BINARY_DIR}/${FILE} COPYONLY) +#endforeach() + set_directory_properties(PROPERTIES TEST_INCLUDE_FILE "${CMAKE_SOURCE_DIR}/EnforceConfig.cmake") # Find all scad files @@ -493,6 +525,9 @@ disable_tests(dumptest_transform-tests # Reenable it when this is improved disable_tests(opencsgtest_child-background) +# FIXME: This single test takes over an hour to run on a 2.7 GHz P4 +disable_tests(opencsgtest_example006 cgalpngtest_example006) + # These tests only makes sense in OpenCSG mode disable_tests(cgalpngtest_child-background cgalpngtest_highlight-and-background-modifier diff --git a/tests/FindGLEW.cmake b/tests/FindGLEW.cmake index 32c2d6e..8093ed3 100644 --- a/tests/FindGLEW.cmake +++ b/tests/FindGLEW.cmake @@ -44,7 +44,8 @@ ENDIF (WIN32) IF (GLEW_INCLUDE_PATH) SET( GLEW_FOUND 1 CACHE STRING "Set to 1 if GLEW is found, 0 otherwise") - MESSAGE(STATUS "GLEW found in " ${GLEW_INCLUDE_PATH} " " ${GLEW_LIBRARY}) + MESSAGE(STATUS "GLEW include found in " ${GLEW_INCLUDE_PATH} ) + MESSAGE(STATUS "GLEW library found in " ${GLEW_LIBRARY} ) ELSE (GLEW_INCLUDE_PATH) SET( GLEW_FOUND 0 CACHE STRING "Set to 1 if GLEW is found, 0 otherwise") ENDIF (GLEW_INCLUDE_PATH) diff --git a/tests/OffscreenContext.h b/tests/OffscreenContext.h index a079c3f..6eebcba 100644 --- a/tests/OffscreenContext.h +++ b/tests/OffscreenContext.h @@ -2,10 +2,12 @@ #define OFFSCREENCONTEXT_H_ #include <iostream> // for error output +#include <string> struct OffscreenContext *create_offscreen_context(int w, int h); void bind_offscreen_context(OffscreenContext *ctx); bool teardown_offscreen_context(OffscreenContext *ctx); bool save_framebuffer(OffscreenContext *ctx, const char *filename); +std::string offscreen_context_getinfo(OffscreenContext *ctx); #endif diff --git a/tests/OffscreenContext.mm b/tests/OffscreenContext.mm index 0c44d7d..eb06615 100644 --- a/tests/OffscreenContext.mm +++ b/tests/OffscreenContext.mm @@ -17,6 +17,15 @@ struct OffscreenContext fbo_t *fbo; }; +string offscreen_context_getinfo(OffscreenContext *ctx) +{ + stringstream out; + out << "GL context creator: Cocoa / CGL\n" + << "PNG generator: Core Foundation\n" + << "OS info: Mac OSX\n" + << "Machine: Apple(TM) Mac(TM)\n"; + return out.str(); +} OffscreenContext *create_offscreen_context(int w, int h) { @@ -84,6 +93,7 @@ bool teardown_offscreen_context(OffscreenContext *ctx) */ bool save_framebuffer(OffscreenContext *ctx, const char *filename) { + if (!ctx || !filename) return false; // Read pixels from OpenGL int samplesPerPixel = 4; // R, G, B and A int rowBytes = samplesPerPixel * ctx->width; diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContextGLX.cc index 839eea9..e607593 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContextGLX.cc @@ -44,6 +44,11 @@ See Also #include <GL/gl.h> #include <GL/glx.h> +#include <assert.h> +#include <sstream> + +#include <sys/utsname.h> // for uname + using namespace std; struct OffscreenContext @@ -66,6 +71,42 @@ void offscreen_context_init(OffscreenContext &ctx, int width, int height) ctx.fbo = NULL; } +string get_os_info() +{ + struct utsname u; + stringstream out; + + if (uname(&u) < 0) + out << "OS info: unknown, uname() error\n"; + else { + out << "OS info: " + << u.sysname << " " + << u.release << " " + << u.version << "\n"; + out << "Machine: " << u.machine; + } + return out.str(); +} + +string offscreen_context_getinfo(OffscreenContext *ctx) +{ + assert(ctx); + + if (!ctx->xdisplay) + return string("No GL Context initialized. No information to report\n"); + + int major, minor; + glXQueryVersion(ctx->xdisplay, &major, &minor); + + stringstream out; + out << "GL context creator: GLX\n" + << "PNG generator: lodepng\n" + << "GLX version: " << major << "." << minor << "\n" + << get_os_info(); + + return out.str(); +} + static XErrorHandler original_xlib_handler = (XErrorHandler) NULL; static bool XCreateWindow_failed = false; static int XCreateWindow_error(Display *dpy, XErrorEvent *event) @@ -94,11 +135,15 @@ bool create_glx_dummy_window(OffscreenContext &ctx) */ int attributes[] = { - GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PIXMAP_BIT | GLX_PBUFFER_BIT, //support all 3, for OpenCSG GLX_RENDER_TYPE, GLX_RGBA_BIT, - GLX_RED_SIZE, 1, - GLX_GREEN_SIZE, 1, - GLX_BLUE_SIZE, 1, + GLX_RED_SIZE, 8, + GLX_GREEN_SIZE, 8, + GLX_BLUE_SIZE, 8, + GLX_ALPHA_SIZE, 8, + GLX_DEPTH_SIZE, 24, // depth-stencil for OpenCSG + GLX_STENCIL_SIZE, 8, + GLX_DOUBLEBUFFER, True, None }; @@ -123,8 +168,9 @@ bool create_glx_dummy_window(OffscreenContext &ctx) Window root = DefaultRootWindow( dpy ); XSetWindowAttributes xwin_attr; - int width = 42; - int height = 42; + int width = ctx.width; + int height = ctx.height; + xwin_attr.background_pixmap = None; xwin_attr.background_pixel = 0; xwin_attr.border_pixel = 0; xwin_attr.colormap = XCreateColormap( dpy, root, visinfo->visual, AllocNone); @@ -137,7 +183,6 @@ bool create_glx_dummy_window(OffscreenContext &ctx) // Window xWin = XCreateSimpleWindow( dpy, DefaultRootWindow(dpy), 0,0,42,42, 0,0,0 ); - XSync( dpy, false ); if ( XCreateWindow_failed ) { XFree( visinfo ); @@ -227,7 +272,6 @@ OffscreenContext *create_offscreen_context(int w, int h) cerr << "Unable to init GLEW: " << glewGetErrorString(err) << endl; return NULL; } - glew_dump(); ctx->fbo = fbo_new(); if (!fbo_init(ctx->fbo, w, h)) { @@ -256,6 +300,7 @@ bool teardown_offscreen_context(OffscreenContext *ctx) */ bool save_framebuffer(OffscreenContext *ctx, const char *filename) { + glXSwapBuffers(ctx->xdisplay, ctx->xwindow); if (!ctx || !filename) return false; int samplesPerPixel = 4; // R, G, B and A GLubyte pixels[ctx->width * ctx->height * samplesPerPixel]; @@ -265,7 +310,7 @@ bool save_framebuffer(OffscreenContext *ctx, const char *filename) int rowBytes = samplesPerPixel * ctx->width; unsigned char *flippedBuffer = (unsigned char *)malloc(rowBytes * ctx->height); if (!flippedBuffer) { - std::cerr << "Unable to allocate flipped buffer for corrected image."; + cerr << "Unable to allocate flipped buffer for corrected image."; return 1; } flip_image(pixels, flippedBuffer, samplesPerPixel, ctx->width, ctx->height); diff --git a/tests/OffscreenContextWGL.cc b/tests/OffscreenContextWGL.cc index 3b966e2..f36671c 100644 --- a/tests/OffscreenContextWGL.cc +++ b/tests/OffscreenContextWGL.cc @@ -22,6 +22,10 @@ For more info: #include <GL/gl.h> // must be included after glew.h +#include <map> +#include <string> +#include <sstream> + using namespace std; struct OffscreenContext @@ -44,6 +48,45 @@ void offscreen_context_init(OffscreenContext &ctx, int width, int height) ctx.fbo = NULL; } +string get_os_info() +{ + OSVERSIONINFO osvi; + + ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&osvi); + + SYSTEM_INFO si; + GetSystemInfo(&si); + map<WORD,const char*> archs; + archs[PROCESSOR_ARCHITECTURE_AMD64] = "amd64"; + archs[PROCESSOR_ARCHITECTURE_IA64] = "itanium"; + archs[PROCESSOR_ARCHITECTURE_INTEL] = "x86"; + archs[PROCESSOR_ARCHITECTURE_UNKNOWN] = "unknown"; + + stringstream out; + out << "OS info: " + << "Microsoft(TM) Windows(TM) " << osvi.dwMajorVersion << " " + << osvi.dwMinorVersion << " " << osvi.dwBuildNumber << " " + << osvi.szCSDVersion; + if (archs.find(si.wProcessorArchitecture) != archs.end()) + out << " " << archs[si.wProcessorArchitecture]; + out << "\n"; + + out << "Machine: " << si.dwProcessorType; + + return out.str(); +} + +string offscreen_context_getinfo(OffscreenContext *ctx) +{ + stringstream out; + out << "GL context creator: WGL\n" + << "PNG generator: lodepng\n" + << get_os_info(); + return out.str(); +} + LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { return DefWindowProc( hwnd, message, wparam, lparam ); @@ -87,11 +130,15 @@ bool create_wgl_dummy_context(OffscreenContext &ctx) ZeroMemory( &pixformat, sizeof( pixformat ) ); pixformat.nSize = sizeof( pixformat ); pixformat.nVersion = 1; - pixformat.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL; + pixformat.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; pixformat.iPixelType = PFD_TYPE_RGBA; - pixformat.cColorBits = 24; - pixformat.cDepthBits = 16; - pixformat.iLayerType = PFD_MAIN_PLANE; + pixformat.cGreenBits = 8; + pixformat.cRedBits = 8; + pixformat.cBlueBits = 8; + pixformat.cAlphaBits = 8; + pixformat.cDepthBits = 24; + pixformat.cStencilBits = 8; + chosenformat = ChoosePixelFormat( dev_context, &pixformat ); if (chosenformat==0) { cerr << "MS GDI - ChoosePixelFormat failed\n"; @@ -142,7 +189,7 @@ OffscreenContext *create_offscreen_context(int w, int h) cerr << "Unable to init GLEW: " << glewGetErrorString(err) << "\n"; return NULL; } - glew_dump(); + //cerr << glew_dump(0); ctx->fbo = fbo_new(); if (!fbo_init(ctx->fbo, w, h)) { @@ -172,6 +219,7 @@ bool teardown_offscreen_context(OffscreenContext *ctx) */ bool save_framebuffer(OffscreenContext *ctx, const char *filename) { + wglSwapLayerBuffers( ctx->dev_context, WGL_SWAP_MAIN_PLANE ); if (!ctx || !filename) return false; int samplesPerPixel = 4; // R, G, B and A vector<GLubyte> pixels(ctx->width * ctx->height * samplesPerPixel); diff --git a/tests/OffscreenView.cc b/tests/OffscreenView.cc index 46951c1..61d5818 100644 --- a/tests/OffscreenView.cc +++ b/tests/OffscreenView.cc @@ -1,11 +1,12 @@ #include <GL/glew.h> #include "OffscreenView.h" -#include <opencsg.h> +#include "system-gl.h" #include "renderer.h" #include <math.h> #include <stdio.h> #include <string.h> #include <cstdlib> +#include <sstream> #define FAR_FAR_AWAY 100000.0 @@ -17,19 +18,6 @@ OffscreenView::OffscreenView(size_t width, size_t height) this->ctx = create_offscreen_context(width, height); if ( this->ctx == NULL ) throw -1; -#ifdef DEBUG - GLint rbits, gbits, bbits, abits, dbits, sbits; - glGetIntegerv(GL_RED_BITS, &rbits); - glGetIntegerv(GL_GREEN_BITS, &gbits); - glGetIntegerv(GL_BLUE_BITS, &bbits); - glGetIntegerv(GL_ALPHA_BITS, &abits); - glGetIntegerv(GL_DEPTH_BITS, &dbits); - glGetIntegerv(GL_STENCIL_BITS, &sbits); - - fprintf(stderr, "FBO: RGBA(%d%d%d%d), depth(%d), stencil(%d)\n", - rbits, gbits, bbits, abits, dbits, sbits); -#endif - initializeGL(); resizeGL(width, height); } @@ -68,105 +56,13 @@ void OffscreenView::initializeGL() glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); glEnable(GL_COLOR_MATERIAL); -#ifdef ENABLE_OPENCSG - const char *openscad_disable_gl20_env = getenv("OPENSCAD_DISABLE_GL20"); - if (openscad_disable_gl20_env && !strcmp(openscad_disable_gl20_env, "0")) - openscad_disable_gl20_env = NULL; - if (glewIsSupported("GL_VERSION_2_0") && openscad_disable_gl20_env == NULL) - { - const char *vs_source = - "uniform float xscale, yscale;\n" - "attribute vec3 pos_b, pos_c;\n" - "attribute vec3 trig, mask;\n" - "varying vec3 tp, tr;\n" - "varying float shading;\n" - "void main() {\n" - " vec4 p0 = gl_ModelViewProjectionMatrix * gl_Vertex;\n" - " vec4 p1 = gl_ModelViewProjectionMatrix * vec4(pos_b, 1.0);\n" - " vec4 p2 = gl_ModelViewProjectionMatrix * vec4(pos_c, 1.0);\n" - " float a = distance(vec2(xscale*p1.x/p1.w, yscale*p1.y/p1.w), vec2(xscale*p2.x/p2.w, yscale*p2.y/p2.w));\n" - " float b = distance(vec2(xscale*p0.x/p0.w, yscale*p0.y/p0.w), vec2(xscale*p1.x/p1.w, yscale*p1.y/p1.w));\n" - " float c = distance(vec2(xscale*p0.x/p0.w, yscale*p0.y/p0.w), vec2(xscale*p2.x/p2.w, yscale*p2.y/p2.w));\n" - " float s = (a + b + c) / 2.0;\n" - " float A = sqrt(s*(s-a)*(s-b)*(s-c));\n" - " float ha = 2.0*A/a;\n" - " gl_Position = p0;\n" - " tp = mask * ha;\n" - " tr = trig;\n" - " vec3 normal, lightDir;\n" - " normal = normalize(gl_NormalMatrix * gl_Normal);\n" - " lightDir = normalize(vec3(gl_LightSource[0].position));\n" - " shading = abs(dot(normal, lightDir));\n" - "}\n"; - - const char *fs_source = - "uniform vec4 color1, color2;\n" - "varying vec3 tp, tr, tmp;\n" - "varying float shading;\n" - "void main() {\n" - " gl_FragColor = vec4(color1.r * shading, color1.g * shading, color1.b * shading, color1.a);\n" - " if (tp.x < tr.x || tp.y < tr.y || tp.z < tr.z)\n" - " gl_FragColor = color2;\n" - "}\n"; - - GLuint vs = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vs, 1, (const GLchar**)&vs_source, NULL); - glCompileShader(vs); - - GLuint fs = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(fs, 1, (const GLchar**)&fs_source, NULL); - glCompileShader(fs); - - GLuint edgeshader_prog = glCreateProgram(); - glAttachShader(edgeshader_prog, vs); - glAttachShader(edgeshader_prog, fs); - glLinkProgram(edgeshader_prog); - - shaderinfo[0] = edgeshader_prog; - shaderinfo[1] = glGetUniformLocation(edgeshader_prog, "color1"); - shaderinfo[2] = glGetUniformLocation(edgeshader_prog, "color2"); - shaderinfo[3] = glGetAttribLocation(edgeshader_prog, "trig"); - shaderinfo[4] = glGetAttribLocation(edgeshader_prog, "pos_b"); - shaderinfo[5] = glGetAttribLocation(edgeshader_prog, "pos_c"); - shaderinfo[6] = glGetAttribLocation(edgeshader_prog, "mask"); - shaderinfo[7] = glGetUniformLocation(edgeshader_prog, "xscale"); - shaderinfo[8] = glGetUniformLocation(edgeshader_prog, "yscale"); - - GLenum err = glGetError(); - if (err != GL_NO_ERROR) { - fprintf(stderr, "OpenGL Error: %s\n", gluErrorString(err)); - } - - GLint status; - glGetProgramiv(edgeshader_prog, GL_LINK_STATUS, &status); - if (status == GL_FALSE) { - int loglen; - char logbuffer[1000]; - glGetProgramInfoLog(edgeshader_prog, sizeof(logbuffer), &loglen, logbuffer); - fprintf(stderr, "OpenGL Program Linker Error:\n%.*s", loglen, logbuffer); - } else { - int loglen; - char logbuffer[1000]; - glGetProgramInfoLog(edgeshader_prog, sizeof(logbuffer), &loglen, logbuffer); - if (loglen > 0) { - fprintf(stderr, "OpenGL Program Link OK:\n%.*s", loglen, logbuffer); - } - glValidateProgram(edgeshader_prog); - glGetProgramInfoLog(edgeshader_prog, sizeof(logbuffer), &loglen, logbuffer); - if (loglen > 0) { - fprintf(stderr, "OpenGL Program Validation results:\n%.*s", loglen, logbuffer); - } - } - } -#endif /* ENABLE_OPENCSG */ + } void OffscreenView::resizeGL(int w, int h) { -#ifdef ENABLE_OPENCSG - shaderinfo[9] = w; - shaderinfo[10] = h; -#endif + this->width = w; + this->height = h; glViewport(0, 0, w, h); w_h_ratio = sqrt((double)w / (double)h); } @@ -237,9 +133,6 @@ void OffscreenView::paintGL() glColor3d(1.0, 0.0, 0.0); if (this->renderer) { -#ifdef ENABLE_OPENCSG - OpenCSG::setContext(0); -#endif this->renderer->draw(showfaces, showedges); } } @@ -249,6 +142,26 @@ bool OffscreenView::save(const char *filename) return save_framebuffer(this->ctx, filename); } +std::string OffscreenView::getInfo() +{ + std::stringstream out; + GLint rbits, gbits, bbits, abits, dbits, sbits; + glGetIntegerv(GL_RED_BITS, &rbits); + glGetIntegerv(GL_GREEN_BITS, &gbits); + glGetIntegerv(GL_BLUE_BITS, &bbits); + glGetIntegerv(GL_ALPHA_BITS, &abits); + glGetIntegerv(GL_DEPTH_BITS, &dbits); + glGetIntegerv(GL_STENCIL_BITS, &sbits); + + out << glew_dump(false) + << "FBO: RGBA(" << rbits << gbits << bbits << abits + << "), depth(" << dbits + << "), stencil(" << sbits << ")\n" + << offscreen_context_getinfo(this->ctx); + + return out.str(); +} + void OffscreenView::setCamera(const Eigen::Vector3d &pos, const Eigen::Vector3d ¢er) { this->camera_eye = pos; diff --git a/tests/OffscreenView.h b/tests/OffscreenView.h index e3c8579..8b98b29 100644 --- a/tests/OffscreenView.h +++ b/tests/OffscreenView.h @@ -4,6 +4,7 @@ #include "OffscreenContext.h" #include <Eigen/Core> #include <Eigen/Geometry> +#include <string> #ifndef _MSC_VER #include <stdint.h> #endif @@ -22,9 +23,12 @@ public: void setupOrtho(bool offset=false); void paintGL(); bool save(const char *filename); + std::string getInfo(); GLint shaderinfo[11]; OffscreenContext *ctx; + size_t width; + size_t height; private: Renderer *renderer; double w_h_ratio; diff --git a/tests/cgalpngtest.cc b/tests/cgalpngtest.cc index 800a829..608fb08 100644 --- a/tests/cgalpngtest.cc +++ b/tests/cgalpngtest.cc @@ -172,16 +172,19 @@ int main(int argc, char **argv) BoundingBox bbox; if (cgalRenderer.polyhedron) { + std::cout << "polyhedron\n" ; CGAL::Bbox_3 cgalbbox = cgalRenderer.polyhedron->bbox(); bbox = BoundingBox(Vector3d(cgalbbox.xmin(), cgalbbox.ymin(), cgalbbox.zmin()), Vector3d(cgalbbox.xmax(), cgalbbox.ymax(), cgalbbox.zmax())); } else if (cgalRenderer.polyset) { + std::cout << "polyset\n" ; bbox = cgalRenderer.polyset->getBoundingBox(); } Vector3d center = getBoundingCenter(bbox); double radius = getBoundingRadius(bbox); + std::cout << "radius: " << radius << "\n"; Vector3d cameradir(1, 1, -0.5); Vector3d camerapos = center - radius*2*cameradir; diff --git a/tests/csgtestcore.cc b/tests/csgtestcore.cc index 864d40e..4cdc5d8 100644 --- a/tests/csgtestcore.cc +++ b/tests/csgtestcore.cc @@ -14,6 +14,7 @@ #include "CGALEvaluator.h" #include "PolySetCGALEvaluator.h" +#include <opencsg.h> #include "OpenCSGRenderer.h" #include "ThrownTogetherRenderer.h" @@ -25,8 +26,15 @@ #include <QDir> #include <QSet> #include <QTimer> + #include <sstream> +#include <vector> + +#include <boost/program_options.hpp> +namespace po = boost::program_options; +using std::string; +using std::vector; using std::cerr; using std::cout; @@ -64,16 +72,181 @@ AbstractNode *find_root_tag(AbstractNode *n) return NULL; } +string info_dump(OffscreenView *glview) +{ + assert(glview); + +#ifdef __GNUG__ +#define compiler_info "GCC " << __VERSION__ +#elif defined(_MSC_VER) +#define compiler_info "MSVC " << _MSC_FULL_VER +#else +#define compiler_info "unknown compiler" +#endif + +#ifndef OPENCSG_VERSION_STRING +#define OPENCSG_VERSION_STRING "unknown, <1.3.2" +#endif + + std::stringstream out; +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + out << "\nOpenSCAD Version: " << TOSTRING(OPENSCAD_VERSION) + << "\nCompiled by: " << compiler_info + << "\nCompile date: " << __DATE__ + << "\nBoost version: " << BOOST_LIB_VERSION + << "\nEigen version: " << EIGEN_WORLD_VERSION << "." + << EIGEN_MAJOR_VERSION << "." << EIGEN_MINOR_VERSION + << "\nCGAL version: " << TOSTRING(CGAL_VERSION) + << "\nOpenCSG version: " << OPENCSG_VERSION_STRING + << "\n" << glview->getInfo() + << "\n"; + + return out.str(); +} + +po::variables_map parse_options(int argc, char *argv[]) +{ + po::options_description desc("Allowed options"); + desc.add_options() + ("help,h", "help message")//; + ("info,i", "information on GLEW, OpenGL, OpenSCAD, and OS")//; + +// po::options_description hidden("Hidden options"); +// hidden.add_options() + ("input-file", po::value< vector<string> >(), "input file") + ("output-file", po::value< vector<string> >(), "ouput file"); + + po::positional_options_description p; + p.add("input-file", 1).add("output-file", 1); + + po::options_description all_options; + all_options.add(desc); // .add(hidden); + + po::variables_map vm; + po::store(po::command_line_parser(argc, argv).options(all_options).positional(p).run(), vm); + po::notify(vm); + + return vm; +} + +void enable_opencsg_shaders( OffscreenView *glview ) +{ + bool ignore_gl_version = true; + const char *openscad_disable_gl20_env = getenv("OPENSCAD_DISABLE_GL20"); + if (openscad_disable_gl20_env && !strcmp(openscad_disable_gl20_env, "0")) + openscad_disable_gl20_env = NULL; + if (glewIsSupported("GL_VERSION_2_0") && openscad_disable_gl20_env == NULL ) + { + const char *vs_source = + "uniform float xscale, yscale;\n" + "attribute vec3 pos_b, pos_c;\n" + "attribute vec3 trig, mask;\n" + "varying vec3 tp, tr;\n" + "varying float shading;\n" + "void main() {\n" + " vec4 p0 = gl_ModelViewProjectionMatrix * gl_Vertex;\n" + " vec4 p1 = gl_ModelViewProjectionMatrix * vec4(pos_b, 1.0);\n" + " vec4 p2 = gl_ModelViewProjectionMatrix * vec4(pos_c, 1.0);\n" + " float a = distance(vec2(xscale*p1.x/p1.w, yscale*p1.y/p1.w), vec2(xscale*p2.x/p2.w, yscale*p2.y/p2.w));\n" + " float b = distance(vec2(xscale*p0.x/p0.w, yscale*p0.y/p0.w), vec2(xscale*p1.x/p1.w, yscale*p1.y/p1.w));\n" + " float c = distance(vec2(xscale*p0.x/p0.w, yscale*p0.y/p0.w), vec2(xscale*p2.x/p2.w, yscale*p2.y/p2.w));\n" + " float s = (a + b + c) / 2.0;\n" + " float A = sqrt(s*(s-a)*(s-b)*(s-c));\n" + " float ha = 2.0*A/a;\n" + " gl_Position = p0;\n" + " tp = mask * ha;\n" + " tr = trig;\n" + " vec3 normal, lightDir;\n" + " normal = normalize(gl_NormalMatrix * gl_Normal);\n" + " lightDir = normalize(vec3(gl_LightSource[0].position));\n" + " shading = abs(dot(normal, lightDir));\n" + "}\n"; + + const char *fs_source = + "uniform vec4 color1, color2;\n" + "varying vec3 tp, tr, tmp;\n" + "varying float shading;\n" + "void main() {\n" + " gl_FragColor = vec4(color1.r * shading, color1.g * shading, color1.b * shading, color1.a);\n" + " if (tp.x < tr.x || tp.y < tr.y || tp.z < tr.z)\n" + " gl_FragColor = color2;\n" + "}\n"; + + GLuint vs = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vs, 1, (const GLchar**)&vs_source, NULL); + glCompileShader(vs); + + GLuint fs = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fs, 1, (const GLchar**)&fs_source, NULL); + glCompileShader(fs); + + GLuint edgeshader_prog = glCreateProgram(); + glAttachShader(edgeshader_prog, vs); + glAttachShader(edgeshader_prog, fs); + glLinkProgram(edgeshader_prog); + + glview->shaderinfo[0] = edgeshader_prog; + glview->shaderinfo[1] = glGetUniformLocation(edgeshader_prog, "color1"); + glview->shaderinfo[2] = glGetUniformLocation(edgeshader_prog, "color2"); + glview->shaderinfo[3] = glGetAttribLocation(edgeshader_prog, "trig"); + glview->shaderinfo[4] = glGetAttribLocation(edgeshader_prog, "pos_b"); + glview->shaderinfo[5] = glGetAttribLocation(edgeshader_prog, "pos_c"); + glview->shaderinfo[6] = glGetAttribLocation(edgeshader_prog, "mask"); + glview->shaderinfo[7] = glGetUniformLocation(edgeshader_prog, "xscale"); + glview->shaderinfo[8] = glGetUniformLocation(edgeshader_prog, "yscale"); + + GLenum err = glGetError(); + if (err != GL_NO_ERROR) { + fprintf(stderr, "OpenGL Error: %s\n", gluErrorString(err)); + } + + GLint status; + glGetProgramiv(edgeshader_prog, GL_LINK_STATUS, &status); + if (status == GL_FALSE) { + int loglen; + char logbuffer[1000]; + glGetProgramInfoLog(edgeshader_prog, sizeof(logbuffer), &loglen, logbuffer); + fprintf(stderr, "OpenGL Program Linker Error:\n%.*s", loglen, logbuffer); + } else { + int loglen; + char logbuffer[1000]; + glGetProgramInfoLog(edgeshader_prog, sizeof(logbuffer), &loglen, logbuffer); + if (loglen > 0) { + fprintf(stderr, "OpenGL Program Link OK:\n%.*s", loglen, logbuffer); + } + glValidateProgram(edgeshader_prog); + glGetProgramInfoLog(edgeshader_prog, sizeof(logbuffer), &loglen, logbuffer); + if (loglen > 0) { + fprintf(stderr, "OpenGL Program Validation results:\n%.*s", loglen, logbuffer); + } + } + } + glview->shaderinfo[9] = glview->width; + glview->shaderinfo[10] = glview->height; +} + int csgtestcore(int argc, char *argv[], test_type_e test_type) { - if (argc != 3) { - fprintf(stderr, "Usage: %s <file.scad> <output.png>\n", argv[0]); + bool sysinfo_dump = false; + const char *filename, *outfilename = NULL; + po::variables_map vm; + try { + vm = parse_options(argc, argv); + } catch ( po::error e ) { + cerr << "error parsing options\n"; + } + if (vm.count("info")) sysinfo_dump = true; + if (vm.count("input-file")) + filename = vm["input-file"].as< vector<string> >().begin()->c_str(); + if (vm.count("output-file")) + outfilename = vm["output-file"].as< vector<string> >().begin()->c_str(); + + if ((!filename || !outfilename) && !sysinfo_dump) { + cerr << "Usage: " << argv[0] << " <file.scad> <output.png>\n"; exit(1); } - const char *filename = argv[1]; - const char *outfilename = argv[2]; - Builtins::instance()->initialize(); QApplication app(argc, argv, false); @@ -107,7 +280,11 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type) AbstractModule *root_module; ModuleInstantiation root_inst; - root_module = parsefile(filename); + if (sysinfo_dump) + root_module = parse("sphere();","",false); + else + root_module = parsefile(filename); + if (!root_module) { exit(1); } @@ -188,6 +365,9 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type) fprintf(stderr,"Can't create OpenGL OffscreenView. Code: %i. Exiting.\n", error); exit(1); } + enable_opencsg_shaders(csgInfo.glview); + + if (sysinfo_dump) cout << info_dump(csgInfo.glview); BoundingBox bbox = csgInfo.root_chain->getBoundingBox(); Vector3d center = (bbox.min() + bbox.max()) / 2; @@ -206,8 +386,11 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type) else csgInfo.glview->setRenderer(&opencsgRenderer); - csgInfo.glview->paintGL(); + OpenCSG::setContext(0); + OpenCSG::setOption(OpenCSG::OffscreenSetting, OpenCSG::FrameBufferObject); + csgInfo.glview->paintGL(); + csgInfo.glview->save(outfilename); delete root_node; diff --git a/tests/fbo.cc b/tests/fbo.cc index 2a3342d..a6677c1 100644 --- a/tests/fbo.cc +++ b/tests/fbo.cc @@ -93,6 +93,7 @@ bool fbo_ext_init(fbo_t *fbo, size_t width, size_t height) glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo->depthbuf_id); if (report_glerror("specifying depth render buffer EXT")) return false; + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo->depthbuf_id); if (report_glerror("specifying stencil render buffer EXT")) return false; @@ -142,7 +143,12 @@ bool fbo_arb_init(fbo_t *fbo, size_t width, size_t height) return false; } - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + //glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + // to prevent Mesa's software renderer from crashing, do this in two stages. + // ie. instead of using GL_DEPTH_STENCIL_ATTACHMENT, do DEPTH then STENCIL. + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + GL_RENDERBUFFER, fbo->depthbuf_id); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fbo->depthbuf_id); if (report_glerror("specifying depth stencil render buffer")) return false; @@ -183,24 +189,25 @@ bool fbo_resize(fbo_t *fbo, size_t width, size_t height) glBindRenderbufferEXT(GL_RENDERBUFFER, fbo->depthbuf_id); if (glewIsSupported("GL_EXT_packed_depth_stencil")) { glRenderbufferStorageEXT(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); - if (report_glerror("creating depth stencil render buffer")) return false; + if (report_glerror("creating EXT depth stencil render buffer")) return false; } else { glRenderbufferStorageEXT(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); - if (report_glerror("creating depth render buffer")) return false; + if (report_glerror("creating EXT depth render buffer")) return false; } glBindRenderbufferEXT(GL_RENDERBUFFER, fbo->renderbuf_id); glRenderbufferStorageEXT(GL_RENDERBUFFER, GL_RGBA8, width, height); - if (report_glerror("creating color render buffer")) return false; + if (report_glerror("creating EXT color render buffer")) return false; } else { + glBindRenderbuffer(GL_RENDERBUFFER, fbo->renderbuf_id); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height); + if (report_glerror("creating color render buffer")) return false; + glBindRenderbuffer(GL_RENDERBUFFER, fbo->depthbuf_id); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); if (report_glerror("creating depth stencil render buffer")) return false; - glBindRenderbuffer(GL_RENDERBUFFER, fbo->renderbuf_id); - glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height); - if (report_glerror("creating color render buffer")) return false; } return true; diff --git a/tests/system-gl.cc b/tests/system-gl.cc index bdf3bf9..2e3f3bc 100644 --- a/tests/system-gl.cc +++ b/tests/system-gl.cc @@ -2,35 +2,40 @@ /* OpenGL helper functions */ #include <iostream> +#include <sstream> +#include <string> #include "system-gl.h" #include <boost/algorithm/string.hpp> using namespace std; using namespace boost; -void glew_dump(bool dumpall) { -#ifdef DEBUG - cerr << "GLEW version: " << glewGetString(GLEW_VERSION) << endl - << "Renderer: " << (const char *)glGetString(GL_RENDERER) << endl - << "Vendor: " << (const char *)glGetString(GL_VENDOR) << endl - << "OpenGL version: " << (const char *)glGetString(GL_VERSION) << endl; +string glew_dump(bool dumpall) +{ + stringstream out; + out << "GLEW version: " << glewGetString(GLEW_VERSION) << endl + << "GL Renderer: " << (const char *)glGetString(GL_RENDERER) << endl + << "GL Vendor: " << (const char *)glGetString(GL_VENDOR) << endl + << "OpenGL Version: " << (const char *)glGetString(GL_VERSION) << endl; + out << "GL Extensions: " << endl; if (dumpall) { string extensions((const char *)glGetString(GL_EXTENSIONS)); replace_all( extensions, " ", "\n " ); - cerr << "Extensions: " << endl << " " << extensions << endl; + out << " " << extensions << endl; } - cerr << " GL_ARB_framebuffer_object: " - << (glewIsSupported("GL_ARB_framebuffer_object") ? "yes" : "no") - << endl - << " GL_EXT_framebuffer_object: " - << (glewIsSupported("GL_EXT_framebuffer_object") ? "yes" : "no") - << endl - << " GL_EXT_packed_depth_stencil: " - << (glewIsSupported("GL_EXT_packed_depth_stencil") ? "yes" : "no") - << endl; -#endif + out << "GL_ARB_framebuffer_object: " + << (glewIsSupported("GL_ARB_framebuffer_object") ? "yes" : "no") + << endl + << "GL_EXT_framebuffer_object: " + << (glewIsSupported("GL_EXT_framebuffer_object") ? "yes" : "no") + << endl + << "GL_EXT_packed_depth_stencil: " + << (glewIsSupported("GL_EXT_packed_depth_stencil") ? "yes" : "no") + << endl; + + return out.str(); }; bool report_glerror(const char * function) diff --git a/tests/system-gl.h b/tests/system-gl.h index b41e32c..4a8ccac 100644 --- a/tests/system-gl.h +++ b/tests/system-gl.h @@ -2,8 +2,9 @@ #define SYSTEMGL_H_ #include <GL/glew.h> +#include <string> -void glew_dump(bool dumpall = false); +std::string glew_dump(bool dumpall=false); bool report_glerror(const char *task); #endif diff --git a/tests/test_cmdline_tool.py b/tests/test_cmdline_tool.py index 8b49f78..ebe802e 100755 --- a/tests/test_cmdline_tool.py +++ b/tests/test_cmdline_tool.py @@ -35,6 +35,7 @@ def init_expected_filename(testname, cmd): global expecteddir, expectedfilename expecteddir = os.path.join(options.regressiondir, os.path.split(cmd)[1]) expectedfilename = os.path.join(expecteddir, testname + "-expected." + options.suffix) + expectedfilename = os.path.normpath( expectedfilename ) def verify_test(testname, cmd): global expectedfilename @@ -66,32 +67,38 @@ def compare_text(expected, actual): return get_normalized_text(expected) == get_normalized_text(actual) def compare_default(resultfilename): + print >> sys.stderr, 'diff text compare: ' + print >> sys.stderr, ' expected textfile: ', expectedfilename + print >> sys.stderr, ' actual textfile: ', resultfilename if not compare_text(expectedfilename, resultfilename): execute_and_redirect("diff", [expectedfilename, resultfilename], sys.stderr) return False return True def compare_png(resultfilename): + #args = [expectedfilename, resultfilename, "-alpha", "Off", "-compose", "difference", "-composite", "-threshold", "10%", "-blur", "2", "-threshold", "30%", "-format", "%[fx:w*h*mean]", "info:"] + #args = [expectedfilename, resultfilename, "-alpha", "Off", "-compose", "difference", "-composite", "-threshold", "10%", "-morphology", "Erode", "Square", "-format", "%[fx:w*h*mean]", "info:"] + # 'morphology' is only available in newer versions of ImageMagick. + # http://www.imagemagick.org/Usage/morphology/#alturnative + args = [expectedfilename, resultfilename, "-alpha", "Off", + "-compose", "difference", "-composite", "-threshold", "10%", + #"-morphology", "Erode", "Square", + "-gaussian-blur","3x65535", "-threshold","99.999%", + "-format", "%[fx:w*h*mean]", "info:"] + print >> sys.stderr, 'ImageMagick image comparison: convert ', ' '.join(args[2:]) + print >> sys.stderr, ' expected image: ', expectedfilename if not resultfilename: - print >> sys.stderr, "Error: OpenSCAD did not generate an image" + print >> sys.stderr, "Error: OpenSCAD did not generate an image to test" return False + print >> sys.stderr, ' actual image: ', resultfilename -# args = [expectedfilename, resultfilename, "-alpha", "Off", "-compose", "difference", "-composite", "-threshold", "10%", "-blur", "2", "-threshold", "30%", "-format", "%[fx:w*h*mean]", "info:"] - args = [expectedfilename, resultfilename, "-alpha", "Off", "-compose", "difference", "-composite", "-threshold", "10%", "-morphology", "Erode", "Square", "-format", "%[fx:w*h*mean]", "info:"] - print >> sys.stderr, 'convert ', ' '.join(args) - (retval, output) = execute_and_redirect("convert", args, subprocess.PIPE) + (retval, output) = execute_and_redirect(options.convert_exec, args, subprocess.PIPE) if retval == 0: pixelerr = int(float(output.strip())) if pixelerr < 32: return True else: print >> sys.stderr, pixelerr, ' pixel errors' return False -# Old compare solution, based on yee_compare -# print >> sys.stderr, 'Yee image compare: ', expectedfilename, ' ', resultfilename -# if execute_and_redirect("./yee_compare", [expectedfilename, resultfilename, "-downsample", "1", "-threshold", "150"], sys.stderr) != 0: -# return False -# return True - def compare_with_expected(resultfilename): if not options.generate: if "compare_" + options.suffix in globals(): return globals()["compare_" + options.suffix](resultfilename) @@ -143,11 +150,12 @@ def usage(): print >> sys.stderr, " -g, --generate Generate expected output for the given tests" print >> sys.stderr, " -s, --suffix=<suffix> Write -expected and -actual files with the given suffix instead of .txt" print >> sys.stderr, " -t, --test=<name> Specify test name instead of deducting it from the argument" + print >> sys.stderr, " -c, --convexec=<name> Path to ImageMagick 'convert' executable" if __name__ == '__main__': # Handle command-line arguments try: - opts, args = getopt.getopt(sys.argv[1:], "gs:t:", ["generate", "suffix=", "test="]) + opts, args = getopt.getopt(sys.argv[1:], "gs:c:t:", ["generate", "convexec=", "suffix=", "test="]) except getopt.GetoptError, err: usage() sys.exit(2) @@ -165,6 +173,8 @@ if __name__ == '__main__': else: options.suffix = a elif o in ("-t", "--test"): options.testname = a + elif o in ("-c", "--convexec"): + options.convert_exec = os.path.normpath( a ) # <cmdline-tool> and <argument> if len(args) < 2: diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py new file mode 100755 index 0000000..e2193e2 --- /dev/null +++ b/tests/test_pretty_print.py @@ -0,0 +1,413 @@ +#!/usr/bin/python + +# Copyright (C) 2011 Don Bright <hugh.m.bright@gmail.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# +# This program 'pretty prints' the ctest output, namely +# files from builddir/Testing/Temporary. +# html & wiki output are produced in Testing/Temporary/wiki +# wiki uploading is available by running +# +# python test_pretty_print.py --upload +# +# Design philosophy +# +# 1. parse the data (images, logs) into easy-to-use data structures +# 2. wikifiy the data +# 3. save the wikified data to disk + +# todo +# repair html output +# do something if tests for GL extensions for OpenCSG fail (test fail, no image production) +# copy all images, sysinfo.txt to bundle for html/upload (images +# can be altered by subsequent runs) +# figure out hwo to make the thing run after the test +# figure out how CTEST treats the logfiles. +# why is hash differing +# instead of having special '-info' prerun, put it as yet-another-test +# and parse the log +# fix windows so that it won't keep asking 'this program crashed' over and over. +# (you can set this in the registry to never happen, but itd be better if the program +# itself was able to disable that temporarily in it's own process) + +import string,sys,re,os,hashlib,subprocess,textwrap + +def tryread(filename): + data = None + try: + f = open(filename,'rb') + data = f.read() + f.close() + except: + print 'couldn\'t open ',filename + return data + +def trysave(filename,data): + try: + if not os.path.isdir(os.path.dirname(filename)): + #print 'creating',os.path.dirname(filename) + os.mkdir(os.path.dirname(filename)) + f=open(filename,'wb') + f.write(data) + f.close() + except: + print 'problem writing to',filename + return None + return True + +def ezsearch(pattern,str): + x = re.search(pattern,str,re.DOTALL|re.MULTILINE) + if x and len(x.groups())>0: return x.group(1).strip() + return '' + +def read_gitinfo(): + # won't work if run from outside of branch. + data = subprocess.Popen(['git','remote','-v'],stdout=subprocess.PIPE).stdout.read() + origin = ezsearch('^origin *?(.*?)\(fetch.*?$',data) + upstream = ezsearch('^upstream *?(.*?)\(fetch.*?$',data) + data = subprocess.Popen(['git','branch'],stdout=subprocess.PIPE).stdout.read() + branch = ezsearch('^\*(.*?)$',data) + out = 'Git branch: ' + branch + ' from origin ' + origin + '\n' + out += 'Git upstream: ' + upstream + '\n' + return out + +def read_sysinfo(filename): + data = tryread(filename) + if not data: return 'sysinfo: unknown' + + machine = ezsearch('Machine:(.*?)\n',data) + machine = machine.replace(' ','-').replace('/','-') + + osinfo = ezsearch('OS info:(.*?)\n',data) + osplain = osinfo.split(' ')[0].strip().replace('/','-') + if 'windows' in osinfo.lower(): osplain = 'win' + + renderer = ezsearch('GL Renderer:(.*?)\n',data) + tmp = renderer.split(' ') + tmp = string.join(tmp[0:3],'-') + tmp = tmp.split('/')[0] + renderer = tmp + + data += read_gitinfo() + + data += 'Image comparison: ImageMagick' + + data = data.strip() + + # create 4 letter hash and stick on end of sysid + nondate_data = re.sub("\n.*?ompile date.*?\n","\n",data).strip() + hexhash = hashlib.md5() + hexhash.update(nondate_data) + hexhash = hexhash.hexdigest()[-4:].upper() + hash = '' + for c in hexhash: hash += chr(ord(c)+97-48) + + sysid = osplain + '_' + machine + '_' + renderer + '_' + hash + sysid = sysid.lower() + + return data, sysid + +class Test: + def __init__(self,fullname,time,passed,output,type,actualfile,expectedfile,scadfile,log): + self.fullname,self.time,self.passed,self.output = \ + fullname, time, passed, output + self.type, self.actualfile, self.expectedfile, self.scadfile = \ + type, actualfile, expectedfile, scadfile + self.fulltestlog = log + + def __str__(self): + x = 'fullname: ' + self.fullname + x+= '\nactualfile: ' + self.actualfile + x+= '\nexpectedfile: ' + self.expectedfile + x+= '\ntesttime: ' + self.time + x+= '\ntesttype: ' + self.type + x+= '\npassed: ' + str(self.passed) + x+= '\nscadfile: ' + self.scadfile + x+= '\noutput bytes: ' + str(len(self.output)) + x+= '\ntestlog bytes: ' + str(len(self.fulltestlog)) + x+= '\n' + return x + +def parsetest(teststring): + patterns = ["Test:(.*?)\n", # fullname + "Test time =(.*?) sec\n", + "Test time.*?Test (Passed)", # pass/fail + "Output:(.*?)<end of output>", + 'Command:.*?-s" "(.*?)"', # type + "actual .*?:(.*?)\n", + "expected .*?:(.*?)\n", + 'Command:.*?(testdata.*?)"' # scadfile + ] + hits = map( lambda pattern: ezsearch(pattern,teststring), patterns ) + test = Test(hits[0],hits[1],hits[2]=='Passed',hits[3],hits[4],hits[5],hits[6],hits[7],teststring) + test.actualfile_data = tryread(test.actualfile) + test.expectedfile_data = tryread(test.expectedfile) + return test + +def parselog(data): + startdate = ezsearch('Start testing: (.*?)\n',data) + enddate = ezsearch('End testing: (.*?)\n',data) + pattern = '([0-9]*/[0-9]* Testing:.*?time elapsed.*?\n)' + test_chunks = re.findall(pattern,data,re.S) + tests = map( parsetest, test_chunks ) + tests = sorted(tests, key = lambda t:t.passed) + return startdate, tests, enddate + +def load_makefiles(builddir): + filelist = [] + for root, dirs, files in os.walk(builddir): + for fname in files: filelist += [ os.path.join(root, fname) ] + files = filter(lambda x: 'build.make' in os.path.basename(x), filelist) + files += filter(lambda x: 'flags.make' in os.path.basename(x), filelist) + files = filter(lambda x: 'esting' not in x and 'emporary' not in x, files) + result = {} + for fname in files: + result[fname.replace(builddir,'')] = open(fname,'rb').read() + return result + +def wikify_filename(fname, wiki_rootpath, sysid): + wikifname = fname.replace('/','_').replace('\\','_').strip('.') + return wiki_rootpath + '_' + sysid + '_' + wikifname + +def towiki(wiki_rootpath, startdate, tests, enddate, sysinfo, sysid, makefiles): + + wiki_template = """ +<h3>[[WIKI_ROOTPATH]] test run report</h3> + +'''Sysid''': SYSID + +'''Result summary''': NUMPASSED / NUMTESTS tests passed ( PERCENTPASSED % ) <br> + +'''System info''': +<pre> +SYSINFO +</pre> + +start time: STARTDATE <br> +end time : ENDDATE <br> + +'''Failed image tests''' + +<REPEAT1> +{| border=1 cellspacing=0 cellpadding=1 +|- +| colspan=2 | FTESTNAME +|- +| Expected image || Actual image +|- +| [[File:EXPECTEDFILE|250px]] || ACTUALFILE_WIKI +|} + +<pre> +TESTLOG +</pre> + + + +</REPEAT1> + + +'''Failed text tests''' + +<REPEAT2> +{|border=1 cellspacing=0 cellpadding=1 +|- +| FTESTNAME +|} + +<pre> +TESTLOG +</pre> + +</REPEAT2> + +'''build.make and flags.make''' +<REPEAT3> +*[[MAKEFILE_NAME]] +</REPEAT3> +""" + txtpages = {} + imgs = {} + passed_tests = filter(lambda x: x.passed, tests) + failed_tests = filter(lambda x: not x.passed, tests) + percent = str(int(100.0*len(passed_tests) / len(tests))) + s = wiki_template + repeat1 = ezsearch('(<REPEAT1>.*?</REPEAT1>)',s) + repeat2 = ezsearch('(<REPEAT2>.*?</REPEAT2>)',s) + repeat3 = ezsearch('(<REPEAT3>.*?</REPEAT3>)',s) + dic = { 'STARTDATE': startdate, 'ENDDATE': enddate, 'WIKI_ROOTPATH': wiki_rootpath, + 'SYSINFO': sysinfo, 'SYSID':sysid, + 'NUMTESTS':len(tests), 'NUMPASSED':len(passed_tests), 'PERCENTPASSED':percent } + for key in dic.keys(): + s = s.replace(key,str(dic[key])) + for t in failed_tests: + if t.type=='txt': + newchunk = re.sub('FTESTNAME',t.fullname,repeat2) + newchunk = newchunk.replace('TESTLOG',t.fulltestlog) + s = s.replace(repeat2, newchunk+repeat2) + elif t.type=='png': + tmp = t.actualfile.replace(builddir,'') + wikiname_a = wikify_filename(tmp,wiki_rootpath,sysid) + tmp = t.expectedfile.replace(os.path.dirname(builddir),'') + wikiname_e = wikify_filename(tmp,wiki_rootpath,sysid) + imgs[wikiname_e] = t.expectedfile_data + if t.actualfile: + actualfile_wiki = '[[File:'+wikiname_a+'|250px]]' + imgs[wikiname_a] = t.actualfile_data + else: + actualfile_wiki = 'No image generated.' + newchunk = re.sub('FTESTNAME',t.fullname,repeat1) + newchunk = newchunk.replace('ACTUALFILE_WIKI',actualfile_wiki) + newchunk = newchunk.replace('EXPECTEDFILE',wikiname_e) + newchunk = newchunk.replace('TESTLOG',t.fulltestlog) + s = s.replace(repeat1, newchunk+repeat1) + + makefiles_wikinames = {} + for mf in sorted(makefiles.keys()): + tmp = mf.replace('CMakeFiles','').replace('.dir','') + wikiname = wikify_filename(tmp,wiki_rootpath,sysid) + newchunk = re.sub('MAKEFILE_NAME',wikiname,repeat3) + s = s.replace(repeat3, newchunk+repeat3) + makefiles_wikinames[mf] = wikiname + + s = s.replace(repeat1,'') + s = s.replace(repeat2,'') + s = s.replace(repeat3,'') + s = re.sub('<REPEAT.*?>\n','',s) + s = re.sub('</REPEAT.*?>','',s) + + mainpage_wikiname = wiki_rootpath + '_' + sysid + '_test_report' + txtpages[ mainpage_wikiname ] = s + for mf in sorted(makefiles.keys()): + txtpages[ makefiles_wikinames[ mf ] ] = '\n*Subreport from [['+mainpage_wikiname+']]\n\n\n<pre>\n'+makefiles[mf]+'\n</pre>' + + return imgs, txtpages + +def wikitohtml(wiki_rootpath, sysid, wikidata, manifest): + # temporarily defunct/broken + head = '<html><head><title>'+wiki_rootpath+' test run for '+sysid +'</title></head><body>' + revmanifest = dict((val,key) for key, val in manifest.iteritems()) + x=re.sub('\{\|(.*?)\n','<table \\1>\n',wikidata) + x=re.sub('\|(.*?colspan.*?)\|','<td \\1>',x) + x=re.sub("'''(.*?)'''","<b>\\1</b>",x) + filestrs=re.findall('\[\[File\:(.*?)\|.*?\]\]',x) + for f in filestrs: + newfile_html='<img src="'+revmanifest[f]+'" width=250 />' + x=re.sub('\[\[File\:'+f+'\|.*?\]\]',newfile_html,x) + dic = { '|}':'</table>', '|-':'<tr>', '||':'<td>', '|':'<td>', + '!!':'<th>', '!':'<tr><th>', '\n\n':'\n<p>\n'} #order matters + for key in dic: x=x.replace(key,dic[key]) + x=re.sub("\[\[(.*?)\]\]","\\1",x) + return head + x + '</body></html>' + +def upload(wikiurl,api_php_path='/',wiki_rootpath='test', sysid='null', botname='cakebaby',botpass='anniew',wikidir='.',dryrun=True): + wetrun = not dryrun + if dryrun: print 'dry run' + try: + import mwclient + except: + print 'please download mwclient and unpack here:', os.getcwd() + sys.exit() + print 'opening site:',wikiurl + if wetrun: + site = mwclient.Site(wikiurl,api_php_path) + + print 'bot login:', botname + if wetrun: site.login(botname,botpass) + + wikifiles = os.listdir(wikidir) + testreport_page = filter( lambda x: 'test_report' in x, wikifiles ) + if (len(testreport_page)>1): + print 'multiple test reports found, please clean dir',wikidir + sys.exit() + rootpage = testreport_page[0] + print 'add',rootpage,' to main report page ',wiki_rootpath + if wetrun: + page = site.Pages[wiki_rootpath] + text = page.edit() + if not '[['+rootpage+']]' in text: + page.save(text +'\n*[['+rootpage+']]\n') + + wikifiles = os.listdir(wikidir) + print 'upload wiki pages:' + for wikiname in wikifiles: + filename = os.path.join(wikidir,wikiname) + filedata = tryread(filename) + print 'upload',len(filedata),'bytes from',wikiname,'...', + sys.stdout.flush() + if wikiname.endswith('.png'): + localf = open(filename,'rb') # mwclient needs file handle + descrip = wiki_rootpath + ' test' + if wetrun: + site.upload(localf,wikiname,descrip,ignore=True) + print 'image uploaded' + else: # textpage + if wetrun: + page = site.Pages[wikiname] + text = page.edit() + page.save(filedata) + print 'text page uploaded' + +def findlogfile(builddir): + logpath = os.path.join(builddir,'Testing','Temporary') + logfilename = os.path.join(logpath,'LastTest.log.tmp') + if not os.path.isfile(logfilename): + logfilename = os.path.join(logpath,'LastTest.log') + if not os.path.isfile(logfilename): + print 'cant find and/or open logfile',logfilename + sys.exit() + return logpath, logfilename + +def main(): + dry = False + print 'running test_pretty_print' + if '--dryrun' in sys.argv: dry=True + suffix = ezsearch('--suffix=(.*?) ',string.join(sys.argv)+' ') + builddir = ezsearch('--builddir=(.*?) ',string.join(sys.argv)+' ') + if builddir=='': builddir=os.getcwd() + print 'build dir set to', builddir + + sysinfo, sysid = read_sysinfo(os.path.join(builddir,'sysinfo.txt')) + makefiles = load_makefiles(builddir) + logpath, logfilename = findlogfile(builddir) + testlog = tryread(logfilename) + startdate, tests, enddate = parselog(testlog) + print 'found sysinfo.txt,', + print 'found', len(makefiles),'makefiles,', + print 'found', len(tests),'test results' + + imgs, txtpages = towiki(wiki_rootpath, startdate, tests, enddate, sysinfo, sysid, makefiles) + + wikidir = os.path.join(logpath,sysid+'_wiki') + print 'writing',len(imgs),'images and',len(txtpages),'wiki pages to:\n ', '.'+wikidir.replace(os.getcwd(),'') + for k in sorted(imgs): trysave( os.path.join(wikidir,k), imgs[k]) + for k in sorted(txtpages): trysave( os.path.join(wikidir,k), txtpages[k]) + + if '--upload' in sys.argv: + upload(wikisite,wiki_api_path,wiki_rootpath,sysid,'openscadbot', + 'tobdacsnepo',wikidir,dryrun=dry) + + print 'test_pretty_print complete' + +#wikisite = 'cakebaby.referata.com' +#wiki_api_path = '' +wikisite = 'cakebaby.wikia.com' +wiki_api_path = '/' +wiki_rootpath = 'OpenSCAD' +builddir = os.getcwd() # os.getcwd()+'/build' + +main() diff --git a/tests/yee_compare.cpp b/tests/yee_compare.cpp deleted file mode 100644 index 9de4720..0000000 --- a/tests/yee_compare.cpp +++ /dev/null @@ -1,681 +0,0 @@ -// modified from PerceptualDiff source for OpenSCAD, 2011 September - -#include "yee_compare.h" -#include "lodepng.h" -#include <cstdlib> -#include <cstring> -#include <cstdio> -#include <math.h> - -static const char* copyright = -"PerceptualDiff version 1.1.1, Copyright (C) 2006 Yangli Hector Yee\n\ -PerceptualDiff comes with ABSOLUTELY NO WARRANTY;\n\ -This is free software, and you are welcome\n\ -to redistribute it under certain conditions;\n\ -See the GPL page for details: http://www.gnu.org/copyleft/gpl.html\n\n"; - -static const char *usage = -"PeceptualDiff image1.tif image2.tif\n\n\ - Compares image1.tif and image2.tif using a perceptually based image metric\n\ - Options:\n\ -\t-verbose : Turns on verbose mode\n\ -\t-fov deg : Field of view in degrees (0.1 to 89.9)\n\ -\t-threshold p : #pixels p below which differences are ignored\n\ -\t-gamma g : Value to convert rgb into linear space (default 2.2)\n\ -\t-luminance l : White luminance (default 100.0 cdm^-2)\n\ -\t-luminanceonly : Only consider luminance; ignore chroma (color) in the comparison\n\ -\t-colorfactor : How much of color to use, 0.0 to 1.0, 0.0 = ignore color.\n\ -\t-downsample : How many powers of two to down sample the image.\n\ -\t-output o.ppm : Write difference to the file o.ppm\n\ -\n\ -\n Note: Input or Output files can also be in the PNG or JPG format or any format\ -\n that FreeImage supports.\ -\n"; - -CompareArgs::CompareArgs() -{ - ImgA = NULL; - ImgB = NULL; - ImgDiff = NULL; - Verbose = false; - LuminanceOnly = false; - FieldOfView = 45.0f; - Gamma = 2.2f; - ThresholdPixels = 100; - Luminance = 100.0f; - ColorFactor = 1.0f; - DownSample = 0; -} - -CompareArgs::~CompareArgs() -{ - if (ImgA) delete ImgA; - if (ImgB) delete ImgB; - if (ImgDiff) delete ImgDiff; -} - -bool CompareArgs::Parse_Args(int argc, char **argv) -{ - if (argc < 3) { - ErrorStr = copyright; - ErrorStr += usage; - return false; - } - int image_count = 0; - const char* output_file_name = NULL; - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "-fov") == 0) { - if (++i < argc) { - FieldOfView = (float) atof(argv[i]); - } - } else if (strcmp(argv[i], "-verbose") == 0) { - Verbose = true; - } else if (strcmp(argv[i], "-threshold") == 0) { - if (++i < argc) { - ThresholdPixels = atoi(argv[i]); - } - } else if (strcmp(argv[i], "-gamma") == 0) { - if (++i < argc) { - Gamma = (float) atof(argv[i]); - } - } else if (strcmp(argv[i], "-luminance") == 0) { - if (++i < argc) { - Luminance = (float) atof(argv[i]); - } - } else if (strcmp(argv[i], "-luminanceonly") == 0) { - LuminanceOnly = true; - } else if (strcmp(argv[i], "-colorfactor") == 0) { - if (++i < argc) { - ColorFactor = (float) atof(argv[i]); - } - } else if (strcmp(argv[i], "-downsample") == 0) { - if (++i < argc) { - DownSample = (int) atoi(argv[i]); - } - } else if (strcmp(argv[i], "-output") == 0) { - if (++i < argc) { - output_file_name = argv[i]; - } - } else if (image_count < 2) { - RGBAImage* img = RGBAImage::ReadFromFile(argv[i]); - if (!img) { - ErrorStr = "FAIL: Cannot open "; - ErrorStr += argv[i]; - ErrorStr += "\n"; - return false; - } else { - ++image_count; - if(image_count == 1) - ImgA = img; - else - ImgB = img; - } - } else { - fprintf(stderr, "Warning: option/file \"%s\" ignored\n", argv[i]); - } - } // i - if(!ImgA || !ImgB) { - ErrorStr = "FAIL: Not enough image files specified\n"; - return false; - } - for (int i = 0; i < DownSample; i++) { - if (Verbose) printf("Downsampling by %d\n", 1 << (i+1)); - RGBAImage *tmp = ImgA->DownSample(); - if (tmp) { - delete ImgA; - ImgA = tmp; - } - tmp = ImgB->DownSample(); - if (tmp) { - delete ImgB; - ImgB = tmp; - } - } - if(output_file_name) { - ImgDiff = new RGBAImage(ImgA->Get_Width(), ImgA->Get_Height(), output_file_name); - } - return true; -} - -void CompareArgs::Print_Args() -{ - printf("Field of view is %f degrees\n", FieldOfView); - printf("Threshold pixels is %d pixels\n", ThresholdPixels); - printf("The Gamma is %f\n", Gamma); - printf("The Display's luminance is %f candela per meter squared\n", Luminance); -} - -////////////////////////////////////////////////////////////////////// -// Construction/Destruction -////////////////////////////////////////////////////////////////////// - -LPyramid::LPyramid(float *image, int width, int height) : - Width(width), - Height(height) -{ - // Make the Laplacian pyramid by successively - // copying the earlier levels and blurring them - for (int i=0; i<MAX_PYR_LEVELS; i++) { - if (i == 0) { - Levels[i] = Copy(image); - } else { - Levels[i] = new float[Width * Height]; - Convolve(Levels[i], Levels[i - 1]); - } - } -} - -LPyramid::~LPyramid() -{ - for (int i=0; i<MAX_PYR_LEVELS; i++) { - if (Levels[i]) delete Levels[i]; - } -} - -float *LPyramid::Copy(float *img) -{ - int max = Width * Height; - float *out = new float[max]; - for (int i = 0; i < max; i++) out[i] = img[i]; - - return out; -} - -void LPyramid::Convolve(float *a, float *b) -// convolves image b with the filter kernel and stores it in a -{ - int y,x,i,j,nx,ny; - const float Kernel[] = {0.05f, 0.25f, 0.4f, 0.25f, 0.05f}; - - for (y=0; y<Height; y++) { - for (x=0; x<Width; x++) { - int index = y * Width + x; - a[index] = 0.0f; - for (i=-2; i<=2; i++) { - for (j=-2; j<=2; j++) { - nx=x+i; - ny=y+j; - if (nx<0) nx=-nx; - if (ny<0) ny=-ny; - if (nx>=Width) nx=2*Width-nx-1; - if (ny>=Height) ny=2*Height-ny-1; - a[index] += Kernel[i+2] * Kernel[j+2] * b[ny * Width + nx]; - } - } - } - } -} - -float LPyramid::Get_Value(int x, int y, int level) -{ - int index = x + y * Width; - int l = level; - if (l > MAX_PYR_LEVELS) l = MAX_PYR_LEVELS; - return Levels[level][index]; -} - - - -#ifndef M_PI -#define M_PI 3.14159265f -#endif - -/* -* Given the adaptation luminance, this function returns the -* threshold of visibility in cd per m^2 -* TVI means Threshold vs Intensity function -* This version comes from Ward Larson Siggraph 1997 -*/ - -float tvi(float adaptation_luminance) -{ - // returns the threshold luminance given the adaptation luminance - // units are candelas per meter squared - - float log_a, r, result; - log_a = log10f(adaptation_luminance); - - if (log_a < -3.94f) { - r = -2.86f; - } else if (log_a < -1.44f) { - r = powf(0.405f * log_a + 1.6f , 2.18f) - 2.86f; - } else if (log_a < -0.0184f) { - r = log_a - 0.395f; - } else if (log_a < 1.9f) { - r = powf(0.249f * log_a + 0.65f, 2.7f) - 0.72f; - } else { - r = log_a - 1.255f; - } - - result = powf(10.0f , r); - - return result; - -} - -// computes the contrast sensitivity function (Barten SPIE 1989) -// given the cycles per degree (cpd) and luminance (lum) -float csf(float cpd, float lum) -{ - float a, b, result; - - a = 440.0f * powf((1.0f + 0.7f / lum), -0.2f); - b = 0.3f * powf((1.0f + 100.0f / lum), 0.15f); - - result = a * cpd * expf(-b * cpd) * sqrtf(1.0f + 0.06f * expf(b * cpd)); - - return result; -} - -/* -* Visual Masking Function -* from Daly 1993 -*/ -float mask(float contrast) -{ - float a, b, result; - a = powf(392.498f * contrast, 0.7f); - b = powf(0.0153f * a, 4.0f); - result = powf(1.0f + b, 0.25f); - - return result; -} - -// convert Adobe RGB (1998) with reference white D65 to XYZ -void AdobeRGBToXYZ(float r, float g, float b, float &x, float &y, float &z) -{ - // matrix is from http://www.brucelindbloom.com/ - x = r * 0.576700f + g * 0.185556f + b * 0.188212f; - y = r * 0.297361f + g * 0.627355f + b * 0.0752847f; - z = r * 0.0270328f + g * 0.0706879f + b * 0.991248f; -} - -void XYZToLAB(float x, float y, float z, float &L, float &A, float &B) -{ - static float xw = -1; - static float yw; - static float zw; - // reference white - if (xw < 0) { - AdobeRGBToXYZ(1, 1, 1, xw, yw, zw); - } - const float epsilon = 216.0f / 24389.0f; - const float kappa = 24389.0f / 27.0f; - float f[3]; - float r[3]; - r[0] = x / xw; - r[1] = y / yw; - r[2] = z / zw; - for (int i = 0; i < 3; i++) { - if (r[i] > epsilon) { - f[i] = powf(r[i], 1.0f / 3.0f); - } else { - f[i] = (kappa * r[i] + 16.0f) / 116.0f; - } - } - L = 116.0f * f[1] - 16.0f; - A = 500.0f * (f[0] - f[1]); - B = 200.0f * (f[1] - f[2]); -} - -bool Yee_Compare(CompareArgs &args) -{ - if ((args.ImgA->Get_Width() != args.ImgB->Get_Width()) || - (args.ImgA->Get_Height() != args.ImgB->Get_Height())) { - args.ErrorStr = "Image dimensions do not match\n"; - return false; - } - - unsigned int i, dim; - dim = args.ImgA->Get_Width() * args.ImgA->Get_Height(); - bool identical = true; - for (i = 0; i < dim; i++) { - if (args.ImgA->Get(i) != args.ImgB->Get(i)) { - identical = false; - break; - } - } - if (identical) { - args.ErrorStr = "Images are binary identical\n"; - return true; - } - - // assuming colorspaces are in Adobe RGB (1998) convert to XYZ - float *aX = new float[dim]; - float *aY = new float[dim]; - float *aZ = new float[dim]; - float *bX = new float[dim]; - float *bY = new float[dim]; - float *bZ = new float[dim]; - float *aLum = new float[dim]; - float *bLum = new float[dim]; - - float *aA = new float[dim]; - float *bA = new float[dim]; - float *aB = new float[dim]; - float *bB = new float[dim]; - - if (args.Verbose) printf("Converting RGB to XYZ\n"); - - unsigned int x, y, w, h; - w = args.ImgA->Get_Width(); - h = args.ImgA->Get_Height(); - for (y = 0; y < h; y++) { - for (x = 0; x < w; x++) { - float r, g, b, l; - i = x + y * w; - r = powf(args.ImgA->Get_Red(i) / 255.0f, args.Gamma); - g = powf(args.ImgA->Get_Green(i) / 255.0f, args.Gamma); - b = powf(args.ImgA->Get_Blue(i) / 255.0f, args.Gamma); - AdobeRGBToXYZ(r,g,b,aX[i],aY[i],aZ[i]); - XYZToLAB(aX[i], aY[i], aZ[i], l, aA[i], aB[i]); - r = powf(args.ImgB->Get_Red(i) / 255.0f, args.Gamma); - g = powf(args.ImgB->Get_Green(i) / 255.0f, args.Gamma); - b = powf(args.ImgB->Get_Blue(i) / 255.0f, args.Gamma); - AdobeRGBToXYZ(r,g,b,bX[i],bY[i],bZ[i]); - XYZToLAB(bX[i], bY[i], bZ[i], l, bA[i], bB[i]); - aLum[i] = aY[i] * args.Luminance; - bLum[i] = bY[i] * args.Luminance; - } - } - - if (args.Verbose) printf("Constructing Laplacian Pyramids\n"); - - LPyramid *la = new LPyramid(aLum, w, h); - LPyramid *lb = new LPyramid(bLum, w, h); - - float num_one_degree_pixels = (float) (2 * tan( args.FieldOfView * 0.5 * M_PI / 180) * 180 / M_PI); - float pixels_per_degree = w / num_one_degree_pixels; - - if (args.Verbose) printf("Performing test\n"); - - float num_pixels = 1; - unsigned int adaptation_level = 0; - for (i = 0; i < MAX_PYR_LEVELS; i++) { - adaptation_level = i; - if (num_pixels > num_one_degree_pixels) break; - num_pixels *= 2; - } - - float cpd[MAX_PYR_LEVELS]; - cpd[0] = 0.5f * pixels_per_degree; - for (i = 1; i < MAX_PYR_LEVELS; i++) cpd[i] = 0.5f * cpd[i - 1]; - float csf_max = csf(3.248f, 100.0f); - - float F_freq[MAX_PYR_LEVELS - 2]; - for (i = 0; i < MAX_PYR_LEVELS - 2; i++) F_freq[i] = csf_max / csf( cpd[i], 100.0f); - - unsigned int pixels_failed = 0; - for (y = 0; y < h; y++) { - for (x = 0; x < w; x++) { - int index = x + y * w; - float contrast[MAX_PYR_LEVELS - 2]; - float sum_contrast = 0; - for (i = 0; i < MAX_PYR_LEVELS - 2; i++) { - float n1 = fabsf(la->Get_Value(x,y,i) - la->Get_Value(x,y,i + 1)); - float n2 = fabsf(lb->Get_Value(x,y,i) - lb->Get_Value(x,y,i + 1)); - float numerator = (n1 > n2) ? n1 : n2; - float d1 = fabsf(la->Get_Value(x,y,i+2)); - float d2 = fabsf(lb->Get_Value(x,y,i+2)); - float denominator = (d1 > d2) ? d1 : d2; - if (denominator < 1e-5f) denominator = 1e-5f; - contrast[i] = numerator / denominator; - sum_contrast += contrast[i]; - } - if (sum_contrast < 1e-5) sum_contrast = 1e-5f; - float F_mask[MAX_PYR_LEVELS - 2]; - float adapt = la->Get_Value(x,y,adaptation_level) + lb->Get_Value(x,y,adaptation_level); - adapt *= 0.5f; - if (adapt < 1e-5) adapt = 1e-5f; - for (i = 0; i < MAX_PYR_LEVELS - 2; i++) { - F_mask[i] = mask(contrast[i] * csf(cpd[i], adapt)); - } - float factor = 0; - for (i = 0; i < MAX_PYR_LEVELS - 2; i++) { - factor += contrast[i] * F_freq[i] * F_mask[i] / sum_contrast; - } - if (factor < 1) factor = 1; - if (factor > 10) factor = 10; - float delta = fabsf(la->Get_Value(x,y,0) - lb->Get_Value(x,y,0)); - bool pass = true; - // pure luminance test - if (delta > factor * tvi(adapt)) { - pass = false; - } else if (!args.LuminanceOnly) { - // CIE delta E test with modifications - float color_scale = args.ColorFactor; - // ramp down the color test in scotopic regions - if (adapt < 10.0f) { - // Don't do color test at all. - color_scale = 0.0; - } - float da = aA[index] - bA[index]; - float db = aB[index] - bB[index]; - da = da * da; - db = db * db; - float delta_e = (da + db) * color_scale; - if (delta_e > factor) { - pass = false; - } - } - if (!pass) { - pixels_failed++; - if (args.ImgDiff) { - args.ImgDiff->Set(255, 0, 0, 255, index); - } - } else { - if (args.ImgDiff) { - args.ImgDiff->Set(0, 0, 0, 255, index); - } - } - } - } - - if (aX) delete[] aX; - if (aY) delete[] aY; - if (aZ) delete[] aZ; - if (bX) delete[] bX; - if (bY) delete[] bY; - if (bZ) delete[] bZ; - if (aLum) delete[] aLum; - if (bLum) delete[] bLum; - if (la) delete la; - if (lb) delete lb; - if (aA) delete aA; - if (bA) delete bA; - if (aB) delete aB; - if (bB) delete bB; - - char different[100]; - sprintf(different, "%d pixels are different\n", pixels_failed); - - // Always output image difference if requested. - if (args.ImgDiff) { - if (args.ImgDiff->WriteToFile(args.ImgDiff->Get_Name().c_str())) { - args.ErrorStr += "Wrote difference image to "; - args.ErrorStr+= args.ImgDiff->Get_Name(); - args.ErrorStr += "\n"; - } else { - args.ErrorStr += "Could not write difference image to "; - args.ErrorStr+= args.ImgDiff->Get_Name(); - args.ErrorStr += "\n"; - } - } - - if (pixels_failed < args.ThresholdPixels) { - args.ErrorStr = "Images are perceptually indistinguishable\n"; - args.ErrorStr += different; - return true; - } - - args.ErrorStr = "Images are visibly different\n"; - args.ErrorStr += different; - - return false; -} - -RGBAImage* RGBAImage::DownSample() const { - if (Width <=1 || Height <=1) return NULL; - int nw = Width / 2; - int nh = Height / 2; - RGBAImage* img = new RGBAImage(nw, nh, Name.c_str()); - for (int y = 0; y < nh; y++) { - for (int x = 0; x < nw; x++) { - int d[4]; - // Sample a 2x2 patch from the parent image. - d[0] = Get(2 * x + 0, 2 * y + 0); - d[1] = Get(2 * x + 1, 2 * y + 0); - d[2] = Get(2 * x + 0, 2 * y + 1); - d[3] = Get(2 * x + 1, 2 * y + 1); - int rgba = 0; - // Find the average color. - for (int i = 0; i < 4; i++) { - int c = (d[0] >> (8 * i)) & 0xFF; - c += (d[1] >> (8 * i)) & 0xFF; - c += (d[2] >> (8 * i)) & 0xFF; - c += (d[3] >> (8 * i)) & 0xFF; - c /= 4; - rgba |= (c & 0xFF) << (8 * i); - } - img->Set(x, y, rgba); - } - } - return img; -} - - -bool RGBAImage::WriteToFile(const char* filename) -{ - LodePNG::Encoder encoder; - encoder.addText("Comment","lodepng"); - encoder.getSettings().zlibsettings.windowSize = 2048; - - -/* - const FREE_IMAGE_FORMAT fileType = FreeImage_GetFIFFromFilename(filename); - if(FIF_UNKNOWN == fileType) - { - printf("Can't save to unknown filetype %s\n", filename); - return false; - } - - FIBITMAP* bitmap = FreeImage_Allocate(Width, Height, 32, 0x000000ff, 0x0000ff00, 0x00ff0000); - if(!bitmap) - { - printf("Failed to create freeimage for %s\n", filename); - return false; - } - - const unsigned int* source = Data; - for( int y=0; y < Height; y++, source += Width ) - { - unsigned int* scanline = (unsigned int*)FreeImage_GetScanLine(bitmap, Height - y - 1 ); - memcpy(scanline, source, sizeof(source[0]) * Width); - } - - FreeImage_SetTransparent(bitmap, false); - FIBITMAP* converted = FreeImage_ConvertTo24Bits(bitmap); - - - const bool result = !!FreeImage_Save(fileType, converted, filename); - if(!result) - printf("Failed to save to %s\n", filename); - - FreeImage_Unload(converted); - FreeImage_Unload(bitmap); - return result; -*/ - return true; -} - -RGBAImage* RGBAImage::ReadFromFile(const char* filename) -{ - unsigned char* buffer; - unsigned char* image; - size_t buffersize, imagesize, i; - LodePNG_Decoder decoder; - - LodePNG_loadFile(&buffer, &buffersize, filename); /*load the image file with given filename*/ - LodePNG_Decoder_init(&decoder); - LodePNG_Decoder_decode(&decoder, &image, &imagesize, buffer, buffersize); /*decode the png*/ - - /*load and decode*/ - /*if there's an error, display it, otherwise display information about the image*/ - if(decoder.error) printf("error %u: %s\n", decoder.error, LodePNG_error_text(decoder.error)); - - int w = decoder.infoPng.width; - int h = decoder.infoPng.height; - - - RGBAImage* result = new RGBAImage(w, h, filename); - // Copy the image over to our internal format, FreeImage has the scanlines bottom to top though. - unsigned int* dest = result->Data; - memcpy(dest, (void *)image, h*w*4); - - /*cleanup decoder*/ - free(image); - free(buffer); - LodePNG_Decoder_cleanup(&decoder); - - return result; -/* - const FREE_IMAGE_FORMAT fileType = FreeImage_GetFileType(filename); - if(FIF_UNKNOWN == fileType) - { - printf("Unknown filetype %s\n", filename); - return 0; - } - - FIBITMAP* freeImage = 0; - if(FIBITMAP* temporary = FreeImage_Load(fileType, filename, 0)) - { - freeImage = FreeImage_ConvertTo32Bits(temporary); - FreeImage_Unload(temporary); - } - if(!freeImage) - { - printf( "Failed to load the image %s\n", filename); - return 0; - } - - const int w = FreeImage_GetWidth(freeImage); - const int h = FreeImage_GetHeight(freeImage); - - RGBAImage* result = new RGBAImage(w, h, filename); - // Copy the image over to our internal format, FreeImage has the scanlines bottom to top though. - unsigned int* dest = result->Data; - for( int y=0; y < h; y++, dest += w ) - { - const unsigned int* scanline = (const unsigned int*)FreeImage_GetScanLine(freeImage, h - y - 1 ); - memcpy(dest, scanline, sizeof(dest[0]) * w); - } - - FreeImage_Unload(freeImage); - return result; - return NULL; -*/ -} - - -int main(int argc, char **argv) -{ - CompareArgs args; - - if (!args.Parse_Args(argc, argv)) { - printf("%s", args.ErrorStr.c_str()); - return -1; - } else { - if (args.Verbose) args.Print_Args(); - } - - const bool passed = Yee_Compare(args); - if (passed) { - if(args.Verbose) - printf("PASS: %s\n", args.ErrorStr.c_str()); - } else { - printf("FAIL: %s\n", args.ErrorStr.c_str()); - } - - return passed ? 0 : 1; -} - diff --git a/tests/yee_compare.h b/tests/yee_compare.h deleted file mode 100644 index 041ae4c..0000000 --- a/tests/yee_compare.h +++ /dev/null @@ -1,126 +0,0 @@ -#ifndef _yee_compare_h -#define _yee_compare_h - -// source code modified for OpenSCAD, Sept 2011 -// original copyright notice follows: -/* -Metric -RGBAImage.h -Comapre Args -Laplacian Pyramid -Copyright (C) 2006 Yangli Hector Yee - -This program is free software; you can redistribute it and/or modify it under the terms of the -GNU General Public License as published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along with this program; -if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include <string> - -class RGBAImage; - -// Args to pass into the comparison function -class CompareArgs -{ -public: - CompareArgs(); - ~CompareArgs(); - bool Parse_Args(int argc, char **argv); - void Print_Args(); - - RGBAImage *ImgA; // Image A - RGBAImage *ImgB; // Image B - RGBAImage *ImgDiff; // Diff image - bool Verbose; // Print lots of text or not - bool LuminanceOnly; // Only consider luminance; ignore chroma channels in the comparison. - float FieldOfView; // Field of view in degrees - float Gamma; // The gamma to convert to linear color space - float Luminance; // the display's luminance - unsigned int ThresholdPixels; // How many pixels different to ignore - std::string ErrorStr; // Error string - // How much color to use in the metric. - // 0.0 is the same as LuminanceOnly = true, - // 1.0 means full strength. - float ColorFactor; - // How much to down sample image before comparing, in powers of 2. - int DownSample; -}; - -#define MAX_PYR_LEVELS 8 - -class LPyramid -{ -public: - LPyramid(float *image, int width, int height); - virtual ~LPyramid(); - float Get_Value(int x, int y, int level); -protected: - float *Copy(float *img); - void Convolve(float *a, float *b); - - // Succesively blurred versions of the original image - float *Levels[MAX_PYR_LEVELS]; - - int Width; - int Height; -}; - -class CompareArgs; - -// Image comparison metric using Yee's method -// References: A Perceptual Metric for Production Testing, Hector Yee, Journal of Graphics Tools 2004 -bool Yee_Compare(CompareArgs &args); - -/** Class encapsulating an image containing R,G,B,A channels. - * - * Internal representation assumes data is in the ABGR format, with the RGB - * color channels premultiplied by the alpha value. Premultiplied alpha is - * often also called "associated alpha" - see the tiff 6 specification for some - * discussion - http://partners.adobe.com/asn/developer/PDFS/TN/TIFF6.pdf - * - */ -class RGBAImage -{ - RGBAImage(const RGBAImage&); - RGBAImage& operator=(const RGBAImage&); -public: - RGBAImage(int w, int h, const char *name = 0) - { - Width = w; - Height = h; - if (name) Name = name; - Data = new unsigned int[w * h]; - }; - ~RGBAImage() { if (Data) delete[] Data; } - unsigned char Get_Red(unsigned int i) { return (Data[i] & 0xFF); } - unsigned char Get_Green(unsigned int i) { return ((Data[i]>>8) & 0xFF); } - unsigned char Get_Blue(unsigned int i) { return ((Data[i]>>16) & 0xFF); } - unsigned char Get_Alpha(unsigned int i) { return ((Data[i]>>24) & 0xFF); } - void Set(unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned int i) - { Data[i] = r | (g << 8) | (b << 16) | (a << 24); } - int Get_Width(void) const { return Width; } - int Get_Height(void) const { return Height; } - void Set(int x, int y, unsigned int d) { Data[x + y * Width] = d; } - unsigned int Get(int x, int y) const { return Data[x + y * Width]; } - unsigned int Get(int i) const { return Data[i]; } - const std::string &Get_Name(void) const { return Name; } - RGBAImage* DownSample() const; - - bool WriteToFile(const char* filename); - static RGBAImage* ReadFromFile(const char* filename); - -protected: - int Width; - int Height; - std::string Name; - unsigned int *Data; -}; - -#endif |