diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/CMakeLists.txt | 89 | ||||
| -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 | 201 | ||||
| -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 | 333 | ||||
| -rw-r--r-- | tests/yee_compare.cpp | 681 | ||||
| -rw-r--r-- | tests/yee_compare.h | 126 | 
16 files changed, 793 insertions, 1011 deletions
| diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 624346b..ac021e5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -75,24 +75,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 +143,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() @@ -185,16 +187,30 @@ 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()  find_package(CGAL REQUIRED) +message(STATUS "CGAL found in ${CGAL_USE_FILE} ${CGAL_INCLUDE_DIRS} ${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) @@ -262,13 +278,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 +303,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,12 +312,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 -# - -add_executable(yee_compare yee_compare.cpp lodepng.cpp) - -#  # dumptest  #  add_executable(dumptest dumptest.cc) @@ -438,13 +449,31 @@ 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} test_pretty_print.py\"") +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 # doesn't work. log is written +  # after all tests run. +") +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 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 60e630c..eabea86 100644 --- a/tests/cgalpngtest.cc +++ b/tests/cgalpngtest.cc @@ -176,16 +176,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 034084c..418738d 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; @@ -41,9 +49,9 @@ public:  	CsgInfo();  	CSGTerm *root_norm_term;          // Normalized CSG products  	class CSGChain *root_chain; -	std::vector<CSGTerm*> highlight_terms; +	vector<CSGTerm*> highlight_terms;  	CSGChain *highlights_chain; -	std::vector<CSGTerm*> background_terms; +	vector<CSGTerm*> background_terms;  	CSGChain *background_chain;  	OffscreenView *glview;  }; @@ -51,9 +59,9 @@ public:  CsgInfo::CsgInfo() {          root_norm_term = NULL;          root_chain = NULL; -        highlight_terms = std::vector<CSGTerm*>(); +        highlight_terms = vector<CSGTerm*>();          highlights_chain = NULL; -        background_terms = std::vector<CSGTerm*>(); +        background_terms = vector<CSGTerm*>();          background_chain = NULL;          glview = NULL;  } @@ -67,16 +75,177 @@ 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 + +	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" << ??? +	    << "\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); @@ -110,7 +279,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);  	} @@ -194,6 +367,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; @@ -212,8 +388,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);  	Builtins::instance(true); 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..2f533ab --- /dev/null +++ b/tests/test_pretty_print.py @@ -0,0 +1,333 @@ +#!/usr/bin/python +# +# This program 'pretty prints' the ctest output, namely +# files from builddir/Testing/Temporary.  +# html & wiki output are produced +# wiki uploading is available by running  +#  +#  python test_pretty_print.py --upload + +# todo +# ban opencsg<2.0 from opencsgtest +# 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 + +import string,sys,re,os,hashlib,subprocess + +def tryread(filename): +	data = None +	try: +		print 'reading', filename +		f = open(filename) +		data = f.read() +		f.close() +	except: +		print 'couldn\'t open ',filename +	return data + +def trysave(data,filename): +	try: +		f=open(filename,'w') +		print 'writing',len(data),'bytes to',filename +		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","",data) +	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) +	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 ) +	print 'found', len(tests),'test results' +	return startdate, tests, enddate + +def wikify_filename(testname,filename,sysid): +	# translate from local system to wiki style filename. +	result = wiki_rootpath+'_'+testname+'_' +	expected = ezsearch('(expected....$)',filename) +	if expected!='': result += expected +	actual = ezsearch(os.sep+'.*?-output.*?(actual.*)',filename) +	if actual!='':  +		result += sysid+'_'+actual +	return result.replace('/','_') + + +def towiki(wiki_rootpath, startdate, tests, enddate, sysinfo, sysid): + +	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''' + +{|border=1 cellspacing=0 cellpadding=1 +! Testname  +<REPEAT2> +|- +| FTESTNAME +</REPEAT2> +|} + + +""" +	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))) +	manifest = {} +	s = wiki_template +	repeat1 = ezsearch('(<REPEAT1>.*?</REPEAT1>)',s) +	repeat2 = ezsearch('(<REPEAT2>.*?</REPEAT2>)',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])) +	testlogs = '' +	for t in failed_tests: +		testlogs += '\n\n'+t.fulltestlog +		if t.type=='txt': +			newchunk = re.sub('FTEST_OUTPUTFILE',t.fullname,repeat2) +			newchunk = re.sub('FTESTNAME',t.fullname,repeat2) +			s = s.replace(repeat2, newchunk+repeat2) +		elif t.type=='png': +			manifest[t.actualfile] = wikify_filename(t.fullname,t.actualfile,sysid) +			manifest[t.expectedfile] = wikify_filename(t.fullname,t.expectedfile,sysid) +			if t.actualfile:  +				actualfile_wiki = '[[File:'+manifest[t.actualfile]+'|250px]]' +			else: +				actualfile_wiki = 'No image generated.' +			newchunk = re.sub('FTESTNAME',t.fullname,repeat1) +			newchunk = newchunk.replace('ACTUALFILE_WIKI',actualfile_wiki) +			newchunk = newchunk.replace('EXPECTEDFILE',manifest[t.expectedfile]) +			newchunk = newchunk.replace('TESTLOG',t.fulltestlog) +			s = s.replace(repeat1, newchunk+repeat1) + +	s = s.replace(repeat1,'') +	s = s.replace(repeat2,'') +	s = re.sub('<REPEAT.*?>\n','',s) +	s = re.sub('</REPEAT.*?>','',s) +	return manifest, s + + +def wikitohtml(wiki_rootpath, sysid, wikidata, manifest): +	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_dryrun(wikiurl,api_php_path,wikidata,manifest,wiki_rootpath,sysid,botname,botpass): +	print 'dry run. no files to be uploaded' +	print 'log in', wikiurl, api_php_path, botname, botpass +	print 'save ' + '*[['+wiki_rootpath+sysid+']]' + ' to page ' + wiki_rootpath +	print 'save ', len(wikidata), ' bytes to page ',wiki_rootpath+sysid +	for localfile in manifest.keys(): +		if localfile: +			localf=open(localfile,'rb') +			wikifile = manifest[localfile] +			print 'upload',localfile,wikifile + +def upload(wikiurl,api_php_path,wikidata,manifest,wiki_rootpath,sysid,botname,botpass,dryrun=True,forceupload=False): +	if dryrun:  +		upload_dryrun(wikiurl,api_php_path,wikidata,manifest,wiki_rootpath,sysid,botname,botpass) +		return None +	try: +		import mwclient +	except: +		print 'please download mwclient and unpack here:', os.cwd() +	print 'opening site:',wikiurl +	if not api_php_path == '': +		site = mwclient.Site(wikiurl,api_php_path) +	else: +		site = mwclient.Site(wikiurl) +		 +	print 'bot login:', botname +	site.login(botname,botpass) + +	print 'edit page:',wiki_rootpath +	page = site.Pages[wiki_rootpath] +	text = page.edit() +	rootpage = wiki_rootpath + sysid +	if not '[['+rootpage+']]' in text: +		page.save(text +'\n*[['+rootpage+']]\n') + +	print 'upload wiki page:',rootpage +	page = site.Pages[rootpage] +	text = page.edit() +	page.save(wikidata) + +	print 'upload images:' +	for localfile in sorted(manifest.keys()): +		if localfile: +			localf = open(localfile,'rb') +			wikifile = manifest[localfile] +			skip=False +			if 'expected.png' in wikifile.lower(): +				image = site.Images[wikifile] +				if image.exists and forceupload==False: +					print 'skipping',wikifile, '(already on wiki)' +					skip=True +			if not skip: +				print 'uploading',wikifile,'...' +				site.upload(localf,wikifile,wiki_rootpath + ' test', ignore=True) + +#wikisite = 'cakebaby.referata.com' +#wiki_api_path = '' +wikisite = 'cakebaby.wikia.com' +wiki_api_path = '/' +wiki_rootpath = 'OpenSCAD' +builddir = os.getcwd() +logpath = os.path.join(builddir,'Testing','Temporary') +logfilename = os.path.join(logpath,'LastTest.log') + +def main(): +	testlog = tryread(logfilename) +	startdate, tests, enddate = parselog(testlog) +	tests = sorted(tests, key = lambda t:t.passed) +	sysinfo, sysid = read_sysinfo('sysinfo.txt') +	if '--forceupload' in sys.argv: forceupl=True +	else: forceupl=False +	manifest, wikidata = towiki(wiki_rootpath, startdate, tests, enddate, sysinfo, sysid) +	trysave(wikidata, os.path.join(logpath,sysid+'.wiki')) +	htmldata = wikitohtml(wiki_rootpath, sysid, wikidata, manifest) +	trysave(htmldata, os.path.join(logpath,sysid+'.html')) +	if '--upload' in sys.argv: +		upload(wikisite,wiki_api_path,wikidata,manifest,wiki_rootpath,sysid,'openscadbot','tobdacsnepo',dryrun=False,forceupload=forceupl) +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 | 
