From eb79c56ece95f5508e9ee12326995a84a1f9a5f2 Mon Sep 17 00:00:00 2001 From: Don Bright Date: Sat, 27 Aug 2011 17:11:47 -0500 Subject: [PATCH 01/93] allow compilation and running on linux. use SDL for OpenGL dummy window --- tests/CMakeLists.txt | 33 +++++++++- tests/FindGLEW.cmake | 9 ++- tests/OffscreenContext.cc | 125 ++++++++++++++++++++++++++++++++++++++ tests/OffscreenContext.h | 6 ++ tests/OffscreenView.cc | 5 +- tests/csgtermtest.cc | 7 +-- 6 files changed, 174 insertions(+), 11 deletions(-) create mode 100644 tests/OffscreenContext.cc diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fb86448d..1d3a004a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -57,6 +57,23 @@ if (NOT OPENCSG_INCLUDE_DIR) endif() include_directories(${OPENCSG_INCLUDE_DIR}) +# SDL (for OpenCSG on Linux & other platforms) +if (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") + message(STATU "SDL not needed for Mac OSX") +else() + find_package(SDL REQUIRED) + if (NOT SDL_FOUND) + message(FATAL_ERROR "SDL not found. needed for OpenCSG testing on this platform") + else() + message(STATUS "SDL library found in " ${SDL_LIBRARY}) + message(STATUS "SDL header found in " ${SDL_INCLUDE_DIR}) + set(OPENGL_LIBRARY ${OPENGL_LIBRARY} ${SDL_LIBRARY}) + endif() +endif() +include_directories(${SDL_INCLUDE_DIR}) + +# GLEW + if (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") set(GLEW_DIR "$ENV{MACOSX_DEPLOY_DIR}") endif() @@ -148,8 +165,14 @@ target_link_libraries(cgaltest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${QT_ # # opencsgtest # -add_executable(opencsgtest opencsgtest.cc OffscreenView.cc OffscreenContext.mm - ../src/opencsgrenderer.cc ../src/throwntogetherrenderer.cc ../src/CSGTermEvaluator.cc ../src/cgal.cc ../src/CGALEvaluator.cc +if (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") + set(OFFSCREEN_SOURCE "OffscreenContext.mm") +else() + set(OFFSCREEN_SOURCE "OffscreenContext.cc") +endif() + +add_executable(opencsgtest opencsgtest.cc OffscreenView.cc ${OFFSCREEN_SOURCE} + ../src/cgal.cc ../src/OpenCSGRenderer.cc ../src/ThrownTogetherRenderer.cc ../src/CSGTermEvaluator.cc ../src/CGALEvaluator.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc ../src/nef2dxf.cc ../src/cgaladv_minkowski2.cc ../src/cgaladv_minkowski3.cc ${COMMON_SOURCES}) @@ -184,7 +207,11 @@ add_cmdline_test(csgtexttest txt ${MINIMAL_FILES}) add_cmdline_test(csgtermtest txt ${MINIMAL_FILES}) # Add cgaltest tests to CTest -LIST(APPEND CGALTEST_FILES ${FEATURES_FILES}) +LIST(APPEND CGALTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/minimal/cube.scad) +LIST(APPEND CGALTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/minimal/sphere.scad) +LIST(APPEND CGALTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/minimal/cylinder.scad) +LIST(APPEND CGALTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/features/background-modifier.scad) +LIST(APPEND CGALTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/features/highlight-modifier.scad) LIST(APPEND CGALTEST_FILES ${CMAKE_SOURCE_DIR}/../examples/example001.scad) add_cmdline_test(cgaltest stl ${CGALTEST_FILES}) diff --git a/tests/FindGLEW.cmake b/tests/FindGLEW.cmake index bccb20a3..a784990a 100644 --- a/tests/FindGLEW.cmake +++ b/tests/FindGLEW.cmake @@ -7,6 +7,9 @@ # GLEW_LIBRARY # +# a few lines of this file are based on the LGPL code found at +# http://openlibraries.org/browser/trunk/FindGLEW.cmake?rev=1383 + IF (WIN32) FIND_PATH( GLEW_INCLUDE_PATH GL/glew.h $ENV{PROGRAMFILES}/GLEW/include @@ -20,15 +23,15 @@ IF (WIN32) ${PROJECT_SOURCE_DIR}/src/nvgl/glew/lib DOC "The GLEW library") ELSE (WIN32) - message(${GLEW_DIR}) + MESSAGE( "-- GLEW_DIR value:" ${GLEW_DIR}) FIND_PATH( GLEW_INCLUDE_PATH GL/glew.h - PATHS + PATHS /usr/include /usr/local/include ${GLEW_DIR}/include NO_DEFAULT_PATH DOC "The directory where GL/glew.h resides") FIND_LIBRARY( GLEW_LIBRARY NAMES GLEW glew - PATHS + PATHS /usr/lib /usr/local/lib ${GLEW_DIR}/lib NO_DEFAULT_PATH DOC "The GLEW library") diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc new file mode 100644 index 00000000..f2a7e9c2 --- /dev/null +++ b/tests/OffscreenContext.cc @@ -0,0 +1,125 @@ +#include "OffscreenContext.h" + +#include +#include // for gluCheckExtension +#include + +// Simple error reporting macros to help keep the sample code clean +#define REPORT_ERROR_AND_EXIT(desc) { std::cout << desc << "\n"; return false; } +#define NULL_ERROR_EXIT(test, desc) { if (!test) REPORT_ERROR_AND_EXIT(desc); } + +struct OffscreenContext +{ + int width; + int height; + GLuint fbo; + GLuint colorbo; + GLuint depthbo; +}; + + +OffscreenContext *create_offscreen_context(int w, int h) +{ + OffscreenContext *ctx = new OffscreenContext; + ctx->width = w; + ctx->height = h; + + // dummy window + SDL_Init(SDL_INIT_VIDEO); + SDL_SetVideoMode(10,10,32,SDL_OPENGL); + + /* + * Test if framebuffer objects are supported + */ + const GLubyte* strExt = glGetString(GL_EXTENSIONS); + GLboolean fboSupported = gluCheckExtension((const GLubyte*)"GL_EXT_framebuffer_object", strExt); + if (!fboSupported) + REPORT_ERROR_AND_EXIT("Your system does not support framebuffer extension - unable to render scene"); + /* + * Create an FBO + */ + GLuint renderBuffer = 0; + GLuint depthBuffer = 0; + // Depth buffer to use for depth testing - optional if you're not using depth testing + glGenRenderbuffersEXT(1, &depthBuffer); + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthBuffer); + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, w, h); + REPORTGLERROR("creating depth render buffer"); + + // Render buffer to use for imaging + glGenRenderbuffersEXT(1, &renderBuffer); + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderBuffer); + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, w, h); + REPORTGLERROR("creating color render buffer"); + ctx->fbo = 0; + glGenFramebuffersEXT(1, &ctx->fbo); + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, ctx->fbo); + REPORTGLERROR("binding framebuffer"); + + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_RENDERBUFFER_EXT, renderBuffer); + REPORTGLERROR("specifying color render buffer"); + + if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != + GL_FRAMEBUFFER_COMPLETE_EXT) + REPORT_ERROR_AND_EXIT("Problem with OpenGL framebuffer after specifying color render buffer."); + + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, + GL_RENDERBUFFER_EXT, depthBuffer); + REPORTGLERROR("specifying depth render buffer"); + + if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != + GL_FRAMEBUFFER_COMPLETE_EXT) + REPORT_ERROR_AND_EXIT("Problem with OpenGL framebuffer after specifying depth render buffer."); + + return ctx; +} + +bool teardown_offscreen_context(OffscreenContext *ctx) +{ + // "un"bind my FBO + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + + /* + * Cleanup + */ + return true; +} + +void write_targa(const char *filename, GLubyte *pixels, int width, int height) +{ + FILE *f = fopen( filename, "w" ); + int y; + if (f) { + GLubyte header[] = { + 00,00,02, 00,00,00, 00,00,00, 00,00,00, + 0xff & width, 0xff & width >> 8, + 0xff & height, 0xff & height >> 8, + 32, 0x20 }; // next-to-last = bit depth + fwrite( header, sizeof(header), 1, f); + for (y=height-1; y>=0; y--) + fwrite( pixels + y*width*4, 4, width, f); + fclose(f); + } +} + +bool save_framebuffer(OffscreenContext *ctx, const char *filename) +{ + /* + * Extract the resulting rendering as an image + */ + + int samplesPerPixel = 4; // R, G, B and A + + GLubyte pixels[ ctx->width * ctx->height * samplesPerPixel ]; + glReadPixels(0, 0, ctx->width, ctx->height, GL_BGRA, GL_UNSIGNED_BYTE, pixels); + printf("writing %s\n",filename); + write_targa(filename,pixels,ctx->width, ctx->height); + + return true; +} + +void bind_offscreen_context(OffscreenContext *ctx) +{ + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, ctx->fbo); +} diff --git a/tests/OffscreenContext.h b/tests/OffscreenContext.h index 0300bcb2..f1c7123a 100644 --- a/tests/OffscreenContext.h +++ b/tests/OffscreenContext.h @@ -1,7 +1,13 @@ #ifndef OFFSCREENCONTEXT_H_ #define OFFSCREENCONTEXT_H_ +#ifdef Q_WS_MAC #include +#else +#include +#include +#endif + #include // for error output #define REPORTGLERROR(task) { GLenum tGLErr = glGetError(); if (tGLErr != GL_NO_ERROR) { std::cout << "OpenGL error " << tGLErr << " while " << task << "\n"; } } diff --git a/tests/OffscreenView.cc b/tests/OffscreenView.cc index f1370417..95659082 100644 --- a/tests/OffscreenView.cc +++ b/tests/OffscreenView.cc @@ -1,8 +1,11 @@ #include #include "OffscreenView.h" #include -#include "Renderer.h" +#include "renderer.h" #include +#include +#include +#include #define FAR_FAR_AWAY 100000.0 diff --git a/tests/csgtermtest.cc b/tests/csgtermtest.cc index 42d22a09..9be09fc0 100644 --- a/tests/csgtermtest.cc +++ b/tests/csgtermtest.cc @@ -122,7 +122,7 @@ int main(int argc, char **argv) AbstractModule *root_module; ModuleInstantiation root_inst; - AbstractNode *root_node; + const AbstractNode *root_node; QFileInfo fileInfo(filename); handle_dep(filename); @@ -154,10 +154,9 @@ int main(int argc, char **argv) // cout << tree.getString(*root_node) << "\n"; - vector highlights; - vector background; CSGTermEvaluator evaluator(tree); - CSGTerm *root_term = evaluator.evaluateCSGTerm(*root_node, highlights, background); + vector empty = vector(); + CSGTerm *root_term = evaluator.evaluateCSGTerm(*root_node, empty, empty); // cout << "Stored terms: " << evaluator.stored_term.size() << "\n"; // for (map::iterator iter = evaluator.stored_term.begin(); From 3783f1ddc2583f16e4944c0e15a79f87b534ab70 Mon Sep 17 00:00:00 2001 From: Don Bright Date: Mon, 12 Sep 2011 17:32:37 -0500 Subject: [PATCH 02/93] merge --- src/openscad.cc | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/openscad.cc b/src/openscad.cc index 32588209..292a263c 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -333,19 +333,6 @@ int main(int argc, char **argv) fclose(fp); } -<<<<<<< HEAD - if (stl_output_file) { - QFile file(stl_output_file); - if (!file.open(QIODevice::ReadWrite)) { - PRINTA("Can't open file \"%1\" for export", stl_output_file); - } - else { - QTextStream fstream(&file); - export_stl(&root_N, fstream, NULL); - file.close(); - } - } -======= if (root_N->dim == 3 && !root_N->p3.is_simple()) { fprintf(stderr, "Object isn't a valid 2-manifold! Modify your design.\n"); exit(1); @@ -353,7 +340,6 @@ int main(int argc, char **argv) if (stl_output_file) export_stl(root_N, stl_output_file, NULL); ->>>>>>> upstream/master if (off_output_file) { QFile file(stl_output_file); From 007c40848db9efd704694f2e7596cabed80da50f Mon Sep 17 00:00:00 2001 From: Don Bright Date: Mon, 12 Sep 2011 17:39:29 -0500 Subject: [PATCH 03/93] merge --- src/openscad.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/openscad.cc b/src/openscad.cc index 292a263c..5ed56c44 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -333,13 +333,13 @@ int main(int argc, char **argv) fclose(fp); } - if (root_N->dim == 3 && !root_N->p3.is_simple()) { + if (root_N.dim == 3 && !root_N.p3.is_simple()) { fprintf(stderr, "Object isn't a valid 2-manifold! Modify your design.\n"); exit(1); } if (stl_output_file) - export_stl(root_N, stl_output_file, NULL); + export_stl(&root_N, stl_output_file, NULL); if (off_output_file) { QFile file(stl_output_file); From 6d70855a4d92a7aafbc21fdd97c570e9ef227182 Mon Sep 17 00:00:00 2001 From: Don Bright Date: Sat, 17 Sep 2011 16:13:37 -0500 Subject: [PATCH 04/93] added png and image comparison, enabling testing on linux --- src/export.cc | 15 - src/openscad.cc | 11 +- src/polyset.cc | 280 -- src/transform.cc | 62 - tests/CMakeLists.txt | 43 +- tests/FindGLEW.cmake | 12 - tests/OffscreenContext.cc | 103 +- tests/cgalpngtest.cc | 2 +- tests/csgtermtest.cc | 6 - tests/lodepng.cpp | 5797 ++++++++++++++++++++++++++++++++++++ tests/lodepng.h | 1893 ++++++++++++ tests/test_cmdline_tool.py | 13 +- tests/yee_compare.cpp | 680 +++++ tests/yee_compare.h | 125 + 14 files changed, 8606 insertions(+), 436 deletions(-) create mode 100644 tests/lodepng.cpp create mode 100644 tests/lodepng.h create mode 100644 tests/yee_compare.cpp create mode 100644 tests/yee_compare.h diff --git a/src/export.cc b/src/export.cc index 9c236009..6c427dd0 100644 --- a/src/export.cc +++ b/src/export.cc @@ -53,17 +53,6 @@ void export_stl(CGAL_Nef_polyhedron *root_N, std::ostream &output, QProgressDial setlocale(LC_NUMERIC, "C"); // Ensure radix is . (not ,) in output -<<<<<<< HEAD - std::ofstream output(filename.toUtf8()); - if (!output.is_open()) { - PRINTA("Can't open STL file \"%1\" for STL export: %2", - filename, QString(strerror(errno))); - set_output_handler(NULL, NULL); - return; - } - -======= ->>>>>>> upstream/visitor output << "solid OpenSCAD_Model\n"; int facet_count = 0; @@ -122,10 +111,6 @@ void export_stl(CGAL_Nef_polyhedron *root_N, std::ostream &output, QProgressDial } output << "endsolid OpenSCAD_Model\n"; -<<<<<<< HEAD - output.close(); -======= ->>>>>>> upstream/visitor setlocale(LC_NUMERIC, ""); // Set default locale } diff --git a/src/openscad.cc b/src/openscad.cc index 203706d8..fd74de4f 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -317,15 +317,6 @@ int main(int argc, char **argv) } } -<<<<<<< HEAD - if (root_N.dim == 3 && !root_N.p3.is_simple()) { - fprintf(stderr, "Object isn't a valid 2-manifold! Modify your design.\n"); - exit(1); - } - - if (stl_output_file) - export_stl(&root_N, stl_output_file, NULL); -======= if (stl_output_file) { if (root_N.dim != 3) { fprintf(stderr, "Current top level object is not a 3D object.\n"); @@ -344,7 +335,6 @@ int main(int argc, char **argv) fstream.close(); } } ->>>>>>> upstream/visitor if (off_output_file) { if (root_N.dim != 3) { @@ -425,3 +415,4 @@ int main(int argc, char **argv) return rc; } + diff --git a/src/polyset.cc b/src/polyset.cc index 7f3818fa..23b98766 100644 --- a/src/polyset.cc +++ b/src/polyset.cc @@ -314,285 +314,5 @@ BoundingBox PolySet::getBoundingBox() const bbox.extend(p); } } -<<<<<<< HEAD -}; - -CGAL_Nef_polyhedron PolySet::render_cgal_nef_polyhedron() const -{ - if (this->is2d) - { -#if 0 - // This version of the code causes problems in some cases. - // Example testcase: import_dxf("testdata/polygon8.dxf"); - // - typedef std::list point_list_t; - typedef point_list_t::iterator point_list_it; - std::list< point_list_t > pdata_point_lists; - std::list < std::pair < point_list_it, point_list_it > > pdata; - Grid2d grid(GRID_COARSE); - - for (int i = 0; i < this->polygons.size(); i++) { - pdata_point_lists.push_back(point_list_t()); - for (int j = 0; j < this->polygons[i].size(); j++) { - double x = this->polygons[i][j].x; - double y = this->polygons[i][j].y; - CGAL_Nef_polyhedron2::Point p; - if (grid.has(x, y)) { - p = grid.data(x, y); - } else { - p = CGAL_Nef_polyhedron2::Point(x, y); - grid.data(x, y) = p; - } - pdata_point_lists.back().push_back(p); - } - pdata.push_back(std::make_pair(pdata_point_lists.back().begin(), - pdata_point_lists.back().end())); - } - - CGAL_Nef_polyhedron2 N(pdata.begin(), pdata.end(), CGAL_Nef_polyhedron2::POLYGONS); - return CGAL_Nef_polyhedron(N); -#endif -#if 0 - // This version of the code works fine but is pretty slow. - // - CGAL_Nef_polyhedron2 N; - Grid2d grid(GRID_COARSE); - - for (int i = 0; i < this->polygons.size(); i++) { - std::list plist; - for (int j = 0; j < this->polygons[i].size(); j++) { - double x = this->polygons[i][j].x; - double y = this->polygons[i][j].y; - CGAL_Nef_polyhedron2::Point p; - if (grid.has(x, y)) { - p = grid.data(x, y); - } else { - p = CGAL_Nef_polyhedron2::Point(x, y); - grid.data(x, y) = p; - } - plist.push_back(p); - } - N += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); - } - - return CGAL_Nef_polyhedron(N); -#endif -#if 1 - // This version of the code does essentially the same thing as the 2nd - // version but merges some triangles before sending them to CGAL. This adds - // complexity but speeds up things.. - // - struct PolyReducer - { - Grid2d grid; - QHash< QPair, QPair > egde_to_poly; - QHash< int, CGAL_Nef_polyhedron2::Point > points; - QHash< int, QList > polygons; - int poly_n; - - void add_edges(int pn) - { - for (int j = 1; j <= this->polygons[pn].size(); j++) { - int a = this->polygons[pn][j-1]; - int b = this->polygons[pn][j % this->polygons[pn].size()]; - if (a > b) { a = a^b; b = a^b; a = a^b; } - if (this->egde_to_poly[QPair(a, b)].first == 0) - this->egde_to_poly[QPair(a, b)].first = pn; - else if (this->egde_to_poly[QPair(a, b)].second == 0) - this->egde_to_poly[QPair(a, b)].second = pn; - else - abort(); - } - } - - void del_poly(int pn) - { - for (int j = 1; j <= this->polygons[pn].size(); j++) { - int a = this->polygons[pn][j-1]; - int b = this->polygons[pn][j % this->polygons[pn].size()]; - if (a > b) { a = a^b; b = a^b; a = a^b; } - if (this->egde_to_poly[QPair(a, b)].first == pn) - this->egde_to_poly[QPair(a, b)].first = 0; - if (this->egde_to_poly[QPair(a, b)].second == pn) - this->egde_to_poly[QPair(a, b)].second = 0; - } - this->polygons.remove(pn); - } - - PolyReducer(const PolySet *ps) : grid(GRID_COARSE), poly_n(1) - { - int point_n = 1; - for (int i = 0; i < ps->polygons.size(); i++) { - for (int j = 0; j < ps->polygons[i].size(); j++) { - double x = ps->polygons[i][j].x; - double y = ps->polygons[i][j].y; - if (this->grid.has(x, y)) { - int idx = this->grid.data(x, y); - // Filter away two vertices with the same index (due to grid) - // This could be done in a more general way, but we'd rather redo the entire - // grid concept instead. - if (this->polygons[this->poly_n].indexOf(idx) == -1) { - this->polygons[this->poly_n].append(this->grid.data(x, y)); - } - } else { - this->grid.align(x, y) = point_n; - this->polygons[this->poly_n].append(point_n); - this->points[point_n] = CGAL_Nef_polyhedron2::Point(x, y); - point_n++; - } - } - if (this->polygons[this->poly_n].size() >= 3) { - add_edges(this->poly_n); - this->poly_n++; - } - else { - this->polygons.remove(this->poly_n); - } - } - } - - int merge(int p1, int p1e, int p2, int p2e) - { - for (int i = 1; i < this->polygons[p1].size(); i++) { - int j = (p1e + i) % this->polygons[p1].size(); - this->polygons[this->poly_n].append(this->polygons[p1][j]); - } - for (int i = 1; i < this->polygons[p2].size(); i++) { - int j = (p2e + i) % this->polygons[p2].size(); - this->polygons[this->poly_n].append(this->polygons[p2][j]); - } - del_poly(p1); - del_poly(p2); - add_edges(this->poly_n); - return this->poly_n++; - } - - void reduce() - { - QList work_queue; - QHashIterator< int, QList > it(polygons); - while (it.hasNext()) { - it.next(); - work_queue.append(it.key()); - } - while (!work_queue.isEmpty()) { - int poly1_n = work_queue.first(); - work_queue.removeFirst(); - if (!this->polygons.contains(poly1_n)) - continue; - for (int j = 1; j <= this->polygons[poly1_n].size(); j++) { - int a = this->polygons[poly1_n][j-1]; - int b = this->polygons[poly1_n][j % this->polygons[poly1_n].size()]; - if (a > b) { a = a^b; b = a^b; a = a^b; } - if (this->egde_to_poly[QPair(a, b)].first != 0 && - this->egde_to_poly[QPair(a, b)].second != 0) { - int poly2_n = this->egde_to_poly[QPair(a, b)].first + - this->egde_to_poly[QPair(a, b)].second - poly1_n; - int poly2_edge = -1; - for (int k = 1; k <= this->polygons[poly2_n].size(); k++) { - int c = this->polygons[poly2_n][k-1]; - int d = this->polygons[poly2_n][k % this->polygons[poly2_n].size()]; - if (c > d) { c = c^d; d = c^d; c = c^d; } - if (a == c && b == d) { - poly2_edge = k-1; - continue; - } - int poly3_n = this->egde_to_poly[QPair(c, d)].first + - this->egde_to_poly[QPair(c, d)].second - poly2_n; - if (poly3_n < 0) - continue; - if (poly3_n == poly1_n) - goto next_poly1_edge; - } - work_queue.append(merge(poly1_n, j-1, poly2_n, poly2_edge)); - goto next_poly1; - } - next_poly1_edge:; - } - next_poly1:; - } - } - - CGAL_Nef_polyhedron2 toNef() - { - CGAL_Nef_polyhedron2 N; - - QHashIterator< int, QList > it(polygons); - while (it.hasNext()) { - it.next(); - std::list plist; - for (int j = 0; j < it.value().size(); j++) { - int p = it.value()[j]; - plist.push_back(points[p]); - } - N += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); - } - - return N; - } - }; - - PolyReducer pr(this); - // printf("Number of polygons before reduction: %d\n", pr.polygons.size()); - pr.reduce(); - // printf("Number of polygons after reduction: %d\n", pr.polygons.size()); - return CGAL_Nef_polyhedron(pr.toNef()); -#endif -#if 0 - // This is another experimental version. I should run faster than the above, - // is a lot simpler and has only one known weakness: Degenerate polygons, which - // get repaired by GLUTess, might trigger a CGAL crash here. The only - // known case for this is triangle-with-duplicate-vertex.dxf - // FIXME: If we just did a projection, we need to recreate the border! - if (this->polygons.size() > 0) assert(this->borders.size() > 0); - CGAL_Nef_polyhedron2 N; - Grid2d grid(GRID_COARSE); - - for (int i = 0; i < this->borders.size(); i++) { - std::list plist; - for (int j = 0; j < this->borders[i].size(); j++) { - double x = this->borders[i][j].x; - double y = this->borders[i][j].y; - CGAL_Nef_polyhedron2::Point p; - if (grid.has(x, y)) { - p = grid.data(x, y); - } else { - p = CGAL_Nef_polyhedron2::Point(x, y); - grid.data(x, y) = p; - } - plist.push_back(p); - } - // FIXME: If a border (path) has a duplicate vertex in dxf, - // the CGAL_Nef_polyhedron2 constructor will crash. - N ^= CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); - } - - return CGAL_Nef_polyhedron(N); - -#endif - } - else // not (this->is2d) - { - CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); - try { - CGAL_Polyhedron P; - CGAL_Build_PolySet builder(this); - P.delegate(builder); -#if 0 - std::cout << P; -#endif - CGAL_Nef_polyhedron3 N(P); - return CGAL_Nef_polyhedron(N); - } - catch (CGAL::Assertion_exception e) { - PRINTF("CGAL error: %s", e.what()); - CGAL::set_error_behaviour(old_behaviour); - return CGAL_Nef_polyhedron(); - } - CGAL::set_error_behaviour(old_behaviour); - } - return CGAL_Nef_polyhedron(); -======= return bbox; ->>>>>>> upstream/visitor } diff --git a/src/transform.cc b/src/transform.cc index 885a7d64..f473f6a4 100644 --- a/src/transform.cc +++ b/src/transform.cc @@ -61,14 +61,7 @@ AbstractNode *TransformModule::evaluate(const Context *ctx, const ModuleInstanti TransformNode *node = new TransformNode(inst); for (int i = 0; i < 16; i++) -<<<<<<< HEAD - node->m[i] = i % 5 == 0 ? 1.0 : 0.0; - for (int i = 16; i < 19; i++) - node->m[i] = -1; - node->m[19] = 1; -======= node->matrix[i] = i % 5 == 0 ? 1.0 : 0.0; ->>>>>>> upstream/visitor std::vector argnames; std::vector argexpr; @@ -220,37 +213,6 @@ AbstractNode *TransformModule::evaluate(const Context *ctx, const ModuleInstanti } } } -<<<<<<< HEAD - else if (this->type == COLOR) - { - Value v = c.lookup_variable("c"); - if (v.type == Value::VECTOR) { - for (int i = 0; i < 4; i++) - node->matrix[16+i] = i < v.vec.size() ? v.vec[i]->num : 1.0; -// FIXME: Port to non-Qt -#if 0 - } else if (v.type == Value::STRING) { - QString colorname = v.text; - QColor color; - color.setNamedColor(colorname); - if (color.isValid()) { - node->matrix[16+0] = color.redF(); - node->matrix[16+1] = color.greenF(); - node->matrix[16+2] = color.blueF(); - } else { - PRINTF_NOCACHE("WARNING: Color name \"%s\" unknown. Please see",v.text.toUtf8().data()); - PRINTF_NOCACHE("WARNING: http://en.wikipedia.org/wiki/Web_colors"); - } -#endif - } - // FIXME: Only lookup alpha if color was set - Value alpha = c.lookup_variable("alpha"); - if (alpha.type == Value::NUMBER) { - node->m[16+3] = alpha.num; - } - } -======= ->>>>>>> upstream/visitor std::vector evaluatednodes = inst->evaluateChildren(); node->children.insert(node->children.end(), evaluatednodes.begin(), evaluatednodes.end()); @@ -280,31 +242,7 @@ std::string TransformNode::toString() const std::string TransformNode::name() const { -<<<<<<< HEAD -<<<<<<< HEAD return "transform"; -======= - if (dump_cache.isEmpty()) { - QString text; - if (m[16] >= 0 || m[17] >= 0 || m[18] >= 0) - text.sprintf("n%d: color([%g, %g, %g, %g])", idx, - m[16], m[17], m[18], m[19]); - else - text.sprintf("n%d: multmatrix([[%g, %g, %g, %g], [%g, %g, %g, %g], " - "[%g, %g, %g, %g], [%g, %g, %g, %g]])", idx, - m[0], m[4], m[ 8], m[12], - m[1], m[5], m[ 9], m[13], - m[2], m[6], m[10], m[14], - m[3], m[7], m[11], m[15]); - text = indent + text + " {\n"; - foreach (AbstractNode *v, children) - text += v->dump(indent + QString("\t")); - ((AbstractNode*)this)->dump_cache = text + indent + "}\n"; - } - return dump_cache; -======= - return "transform"; ->>>>>>> upstream/visitor } void register_builtin_transform() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1874669c..ed492bea 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -63,7 +63,7 @@ if (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") else() find_package(SDL REQUIRED) if (NOT SDL_FOUND) - message(FATAL_ERROR "SDL not found. needed for OpenCSG testing on this platform") + message(FATAL_ERROR "SDL not found.") else() message(STATUS "SDL library found in " ${SDL_LIBRARY}) message(STATUS "SDL header found in " ${SDL_INCLUDE_DIR}) @@ -129,9 +129,25 @@ set(COMMON_SOURCES ../src/PolySetEvaluator.cc ../src/PolySetCache.cc ../src/Tree.cc + lodepng.cpp ${FLEX_OpenSCADlexer_OUTPUTS} ${BISON_OpenSCADparser_OUTPUTS}) +# +# Offscreen OpenGL context source code +# +if (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") + set(OFFSCREEN_CTX_SOURCE "OffscreenContext.mm") +else() + set(OFFSCREEN_CTX_SOURCE "OffscreenContext.cc") +endif() + +# +# Yangli Hector Yee's comparison aglorithm +# + +add_executable(yee_compare yee_compare.cpp lodepng.cpp) + # # dumptest # @@ -170,7 +186,7 @@ target_link_libraries(cgaltest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${QT_ # # cgalpngtest # -add_executable(cgalpngtest cgalpngtest.cc OffscreenView.cc OffscreenContext.mm +add_executable(cgalpngtest cgalpngtest.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} ../src/CGALRenderer.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CSGTermEvaluator.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc @@ -182,24 +198,12 @@ target_link_libraries(cgalpngtest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${ # # opencsgtest # -<<<<<<< HEAD -if (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") - set(OFFSCREEN_SOURCE "OffscreenContext.mm") -else() - set(OFFSCREEN_SOURCE "OffscreenContext.cc") -endif() -add_executable(opencsgtest opencsgtest.cc OffscreenView.cc ${OFFSCREEN_SOURCE} - ../src/cgal.cc ../src/OpenCSGRenderer.cc ../src/ThrownTogetherRenderer.cc ../src/CSGTermEvaluator.cc ../src/CGALEvaluator.cc - ../src/PolySetCGALEvaluator.cc ../src/qhash.cc ../src/nef2dxf.cc - ../src/cgaladv_minkowski2.cc ../src/cgaladv_minkowski3.cc -======= -add_executable(opencsgtest opencsgtest.cc OffscreenView.cc OffscreenContext.mm +add_executable(opencsgtest opencsgtest.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} ../src/OpenCSGRenderer.cc ../src/ThrownTogetherRenderer.cc ../src/CSGTermEvaluator.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc ../src/CGAL_Nef_polyhedron_DxfData.cc ../src/cgaladv_minkowski2.cc ../src/cgaladv_convexhull2.cc ->>>>>>> upstream/visitor ${COMMON_SOURCES}) set_target_properties(opencsgtest PROPERTIES COMPILE_FLAGS "-DENABLE_OPENCSG -DENABLE_CGAL ${CGAL_CXX_FLAGS_INIT}") target_link_libraries(opencsgtest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${QT_LIBRARIES} ${OPENCSG_LIBRARY} ${GLEW_LIBRARY} ${COCOA_LIBRARY} ${OPENGL_LIBRARY}) @@ -232,15 +236,6 @@ add_cmdline_test(csgtexttest txt ${MINIMAL_FILES}) add_cmdline_test(csgtermtest txt ${MINIMAL_FILES}) # Add cgaltest tests to CTest -<<<<<<< HEAD -LIST(APPEND CGALTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/minimal/cube.scad) -LIST(APPEND CGALTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/minimal/sphere.scad) -LIST(APPEND CGALTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/minimal/cylinder.scad) -LIST(APPEND CGALTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/features/background-modifier.scad) -LIST(APPEND CGALTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/features/highlight-modifier.scad) -LIST(APPEND CGALTEST_FILES ${CMAKE_SOURCE_DIR}/../examples/example001.scad) -======= ->>>>>>> upstream/visitor add_cmdline_test(cgaltest stl ${CGALTEST_FILES}) # Add cgalpngtest tests to CTest diff --git a/tests/FindGLEW.cmake b/tests/FindGLEW.cmake index edf590ca..c2dc6a25 100644 --- a/tests/FindGLEW.cmake +++ b/tests/FindGLEW.cmake @@ -23,26 +23,14 @@ IF (WIN32) ${PROJECT_SOURCE_DIR}/src/nvgl/glew/lib DOC "The GLEW library") ELSE (WIN32) -<<<<<<< HEAD - MESSAGE( "-- GLEW_DIR value:" ${GLEW_DIR}) - FIND_PATH( GLEW_INCLUDE_PATH GL/glew.h - PATHS /usr/include /usr/local/include - ${GLEW_DIR}/include -======= message("GLEW_DIR: " ${GLEW_DIR}) FIND_PATH( GLEW_INCLUDE_PATH GL/glew.h PATHS ${GLEW_DIR}/include /usr/include /usr/local/include ->>>>>>> upstream/visitor NO_DEFAULT_PATH DOC "The directory where GL/glew.h resides") FIND_LIBRARY( GLEW_LIBRARY NAMES GLEW glew -<<<<<<< HEAD - PATHS /usr/lib /usr/local/lib - ${GLEW_DIR}/lib -======= PATHS ${GLEW_DIR}/lib /usr/lib /usr/local/lib ->>>>>>> upstream/visitor NO_DEFAULT_PATH DOC "The GLEW library") ENDIF (WIN32) diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc index 0795fe71..d5c4c097 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContext.cc @@ -1,4 +1,6 @@ #include "OffscreenContext.h" +#include "printutils.h" +#include "lodepng.h" // see http://www.gamedev.net/topic/552607-conflict-between-glew-and-sdl/ #define NO_SDL_GLEXT @@ -25,6 +27,52 @@ struct OffscreenContext GLuint depthbo; }; +void write_targa(const char *filename, GLubyte *pixels, int width, int height) +{ + FILE *f = fopen( filename, "w" ); + int y; + if (f) { + GLubyte header[] = { + 00,00,02, 00,00,00, 00,00,00, 00,00,00, + 0xff & width, 0xff & width >> 8, + 0xff & height, 0xff & height >> 8, + 32, 0x20 }; // next-to-last = bit depth + fwrite( header, sizeof(header), 1, f); + for (y=height-1; y>=0; y--) + fwrite( pixels + y*width*4, 4, width, f); + fclose(f); + } +} + +void write_png(const char *filename, GLubyte *pixels, int width, int height) +{ + size_t pixel_size = 4; + size_t dataout_size = -1; + GLubyte *dataout = (GLubyte*)malloc(width*height*pixel_size); // freed below + GLubyte *pixels_flipped = (GLubyte*)malloc(width*height*pixel_size); // freed below + for (int y=0;ywidth,ctx->height,32,SDL_OPENGL); // must come after openGL context init (done by dummy window) // but must also come before various EXT calls - glewInit(); + //glewInit(); /* // Test if framebuffer objects are supported @@ -84,8 +132,27 @@ OffscreenContext *create_offscreen_context(int w, int h) if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) REPORT_ERROR_AND_EXIT("Problem with OpenGL framebuffer after specifying depth render buffer."); +*/ +/* + glClearColor(1, 1, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + glBegin(GL_TRIANGLES); + glColor3f( 1, 0, 0); + glVertex3f( 0, 0, 0); + glVertex3f( 1, 0, 0); + glVertex3f( 0, 1, 0); + glEnd(); + SDL_GL_SwapBuffers(); +// sleep(2); */ + int samplesPerPixel = 4; // R, G, B and A + +/* char * filename = "blah.tga"; + GLubyte pixels[ ctx->width * ctx->height * samplesPerPixel ]; + glReadPixels(0, 0, ctx->width, ctx->height, GL_BGRA, GL_UNSIGNED_BYTE, pixels); + printf("writing %s\n",filename); + write_targa(filename,pixels,ctx->width, ctx->height);*/ return ctx; } @@ -100,35 +167,25 @@ bool teardown_offscreen_context(OffscreenContext *ctx) return true; } -void write_targa(const char *filename, GLubyte *pixels, int width, int height) -{ - FILE *f = fopen( filename, "w" ); - int y; - if (f) { - GLubyte header[] = { - 00,00,02, 00,00,00, 00,00,00, 00,00,00, - 0xff & width, 0xff & width >> 8, - 0xff & height, 0xff & height >> 8, - 32, 0x20 }; // next-to-last = bit depth - fwrite( header, sizeof(header), 1, f); - for (y=height-1; y>=0; y--) - fwrite( pixels + y*width*4, 4, width, f); - fclose(f); - } -} - bool save_framebuffer(OffscreenContext *ctx, const char *filename) { /* * Extract the resulting rendering as an image */ - int samplesPerPixel = 4; // R, G, B and A + SDL_GL_SwapBuffers(); // show image + int samplesPerPixel = 4; // R, G, B and A GLubyte pixels[ ctx->width * ctx->height * samplesPerPixel ]; - glReadPixels(0, 0, ctx->width, ctx->height, GL_BGRA, GL_UNSIGNED_BYTE, pixels); - printf("writing %s\n",filename); - write_targa(filename,pixels,ctx->width, ctx->height); + glReadPixels(0, 0, ctx->width, ctx->height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + //char * filename2="blah2.tga"; + //PRINTF("writing %s\n",filename2); + //write_targa(filename2,pixels,ctx->width, ctx->height); + char * filename2="blah2.png"; + PRINTF("writing %s . . .",filename); + //write_targa(filename2,pixels,ctx->width, ctx->height); + write_png(filename,pixels,ctx->width, ctx->height); + PRINTF("written\n"); return true; } diff --git a/tests/cgalpngtest.cc b/tests/cgalpngtest.cc index 4c5c914a..d7c6886c 100644 --- a/tests/cgalpngtest.cc +++ b/tests/cgalpngtest.cc @@ -180,7 +180,7 @@ int main(int argc, char **argv) csgInfo.glview = new OffscreenView(512,512); - glewInit(); + //glewInit(); #ifdef DEBUG cout << "GLEW version " << glewGetString(GLEW_VERSION) << "\n"; cout << (const char *)glGetString(GL_RENDERER) << "(" << (const char *)glGetString(GL_VENDOR) << ")\n" diff --git a/tests/csgtermtest.cc b/tests/csgtermtest.cc index f23ec70a..eec5d4bb 100644 --- a/tests/csgtermtest.cc +++ b/tests/csgtermtest.cc @@ -144,17 +144,11 @@ int main(int argc, char **argv) // cout << tree.getString(*root_node) << "\n"; -<<<<<<< HEAD - CSGTermEvaluator evaluator(tree); - vector empty = vector(); - CSGTerm *root_term = evaluator.evaluateCSGTerm(*root_node, empty, empty); -======= vector highlights; vector background; PolySetEvaluator psevaluator(tree); CSGTermEvaluator evaluator(tree, &psevaluator); CSGTerm *root_term = evaluator.evaluateCSGTerm(*root_node, highlights, background); ->>>>>>> upstream/visitor // cout << "Stored terms: " << evaluator.stored_term.size() << "\n"; // for (map::iterator iter = evaluator.stored_term.begin(); diff --git a/tests/lodepng.cpp b/tests/lodepng.cpp new file mode 100644 index 00000000..831194fe --- /dev/null +++ b/tests/lodepng.cpp @@ -0,0 +1,5797 @@ +/* +LodePNG version 20110908 + +Copyright (c) 2005-2011 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +/* +The manual and changelog are in the header file "lodepng.h" +Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for C. +*/ + +#include "lodepng.h" + +#include +#include + +#ifdef __cplusplus +#include +#endif /*__cplusplus*/ + +#define VERSION_STRING "20110908" + +/* +This source file is built up in the following large parts. The code sections +with the "LODEPNG_COMPILE_" #defines divide this up further in an intermixed way. +-Tools for C and common code for PNG and Zlib +-C Code for Zlib (huffman, deflate, ...) +-C Code for PNG (file format chunks, adam7, PNG filters, color conversions, ...) +-The C++ wrapper around all of the above +*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // Tools for C, and common code for PNG and Zlib. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Often in case of an error a value is assigned to a variable and then it breaks +out of a loop (to go to the cleanup phase of a function). This macro does that. +It makes the error handling code shorter and more readable. + +Example: if(!uivector_resizev(&frequencies_ll, 286, 0)) ERROR_BREAK(9924); +*/ +#define CERROR_BREAK(errorvar, code)\ +{\ + errorvar = code;\ + break;\ +} + +/*version of CERROR_BREAK that assumes the common case where the error variable is named "error"*/ +#define ERROR_BREAK(code) CERROR_BREAK(error, code) + +/* +About vector, uivector, ucvector and string: +-All of them wrap dynamic arrays or text strings in a similar way. +-LodePNG was originally written in C++. The vectors replace the std::vectors that were used in the C++ version. +-The string tools are made to avoid problems with compilers that declare things like strncat as deprecated. +-They're not used in the interface, only internally in this file as static functions. +-As with many other structs in this file, the init and cleanup functions serve as ctor and dtor. +*/ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER + +typedef struct vector /*dynamic vector of void* pointers. This one is used only by the deflate compressor*/ +{ + void* data; + size_t size; /*in groups of bytes depending on type*/ + size_t allocsize; /*in bytes*/ + unsigned typesize; /*sizeof the type you store in data*/ +} vector; + +static unsigned vector_resize(vector* p, size_t size) /*returns 1 if success, 0 if failure ==> nothing done*/ +{ + if(size * p->typesize > p->allocsize) + { + size_t newsize = size * p->typesize * 2; + void* data = realloc(p->data, newsize); + if(data) + { + p->allocsize = newsize; + p->data = data; + p->size = size; + } + else return 0; + } + else p->size = size; + return 1; +} + +/*resize and use destructor on elements if it gets smaller*/ +static unsigned vector_resized(vector* p, size_t size, void dtor(void*)) +{ + size_t i; + if(size < p->size) + { + for(i = size; i < p->size; i++) + { + dtor(&((char*)(p->data))[i * p->typesize]); + } + } + return vector_resize(p, size); +} + +static void vector_cleanup(void* p) +{ + ((vector*)p)->size = ((vector*)p)->allocsize = 0; + free(((vector*)p)->data); + ((vector*)p)->data = NULL; +} + +static void vector_cleanupd(vector* p, void dtor(void*)) /*clear and use destructor on elements*/ +{ + vector_resized(p, 0, dtor); + vector_cleanup(p); +} + +static void vector_init(vector* p, unsigned typesize) +{ + p->data = NULL; + p->size = p->allocsize = 0; + p->typesize = typesize; +} + +static void vector_swap(vector* p, vector* q) /*they're supposed to have the same typesize*/ +{ + size_t tmp; + void* tmpp; + tmp = p->size; p->size = q->size; q->size = tmp; + tmp = p->allocsize; p->allocsize = q->allocsize; q->allocsize = tmp; + tmpp = p->data; p->data = q->data; q->data = tmpp; +} + +static void* vector_get(vector* p, size_t index) +{ + return &((char*)p->data)[index * p->typesize]; +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* /////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ZLIB +/*dynamic vector of unsigned ints*/ +typedef struct uivector +{ + unsigned* data; + size_t size; /*size in number of unsigned longs*/ + size_t allocsize; /*allocated size in bytes*/ +} uivector; + +static void uivector_cleanup(void* p) +{ + ((uivector*)p)->size = ((uivector*)p)->allocsize = 0; + free(((uivector*)p)->data); + ((uivector*)p)->data = NULL; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_resize(uivector* p, size_t size) +{ + if(size * sizeof(unsigned) > p->allocsize) + { + size_t newsize = size * sizeof(unsigned) * 2; + void* data = realloc(p->data, newsize); + if(data) + { + p->allocsize = newsize; + p->data = (unsigned*)data; + p->size = size; + } + else return 0; + } + else p->size = size; + return 1; +} + +/*resize and give all new elements the value*/ +static unsigned uivector_resizev(uivector* p, size_t size, unsigned value) +{ + size_t oldsize = p->size, i; + if(!uivector_resize(p, size)) return 0; + for(i = oldsize; i < size; i++) p->data[i] = value; + return 1; +} + +static void uivector_init(uivector* p) +{ + p->data = NULL; + p->size = p->allocsize = 0; +} + +#ifdef LODEPNG_COMPILE_ENCODER +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_push_back(uivector* p, unsigned c) +{ + if(!uivector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} + +/*copy q to p, returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_copy(uivector* p, const uivector* q) +{ + size_t i; + if(!uivector_resize(p, q->size)) return 0; + for(i = 0; i < q->size; i++) p->data[i] = q->data[i]; + return 1; +} + +static void uivector_swap(uivector* p, uivector* q) +{ + size_t tmp; + unsigned* tmpp; + tmp = p->size; p->size = q->size; q->size = tmp; + tmp = p->allocsize; p->allocsize = q->allocsize; q->allocsize = tmp; + tmpp = p->data; p->data = q->data; q->data = tmpp; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* /////////////////////////////////////////////////////////////////////////// */ + +/*dynamic vector of unsigned chars*/ +typedef struct ucvector +{ + unsigned char* data; + size_t size; /*used size*/ + size_t allocsize; /*allocated size*/ +} ucvector; + +static void ucvector_cleanup(void* p) +{ + ((ucvector*)p)->size = ((ucvector*)p)->allocsize = 0; + free(((ucvector*)p)->data); + ((ucvector*)p)->data = NULL; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_resize(ucvector* p, size_t size) +{ + if(size * sizeof(unsigned char) > p->allocsize) + { + size_t newsize = size * sizeof(unsigned char) * 2; + void* data = realloc(p->data, newsize); + if(data) + { + p->allocsize = newsize; + p->data = (unsigned char*)data; + p->size = size; + } + else return 0; /*error: not enough memory*/ + } + else p->size = size; + return 1; +} + +#ifdef LODEPNG_COMPILE_DECODER +#ifdef LODEPNG_COMPILE_PNG +/*resize and give all new elements the value*/ +static unsigned ucvector_resizev(ucvector* p, size_t size, unsigned char value) +{ + size_t oldsize = p->size, i; + if(!ucvector_resize(p, size)) return 0; + for(i = oldsize; i < size; i++) p->data[i] = value; + return 1; +} +#endif /*LODEPNG_COMPILE_PNG*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + +static void ucvector_init(ucvector* p) +{ + p->data = NULL; + p->size = p->allocsize = 0; +} + +#ifdef LODEPNG_COMPILE_ZLIB +/*you can both convert from vector to buffer&size and vica versa. If you use +init_buffer to take over a buffer and size, it is not needed to use cleanup*/ +static void ucvector_init_buffer(ucvector* p, unsigned char* buffer, size_t size) +{ + p->data = buffer; + p->allocsize = p->size = size; +} +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_push_back(ucvector* p, unsigned char c) +{ + if(!ucvector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned string_resize(char** out, size_t size) +{ + char* data = (char*)realloc(*out, size + 1); + if(data) + { + data[size] = 0; /*null termination char*/ + *out = data; + } + return data != 0; +} + +/*init a {char*, size_t} pair for use as string*/ +static void string_init(char** out) +{ + *out = NULL; + string_resize(out, 0); +} + +/*free the above pair again*/ +static void string_cleanup(char** out) +{ + free(*out); + *out = NULL; +} + +static void string_set(char** out, const char* in) +{ + size_t insize = strlen(in), i = 0; + if(string_resize(out, insize)) + { + for(i = 0; i < insize; i++) + { + (*out)[i] = in[i]; + } + } +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +unsigned LodePNG_read32bitInt(const unsigned char* buffer) +{ + return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; +} + +/*buffer must have at least 4 allocated bytes available*/ +static void LodePNG_set32bitInt(unsigned char* buffer, unsigned value) +{ + buffer[0] = (unsigned char)((value >> 24) & 0xff); + buffer[1] = (unsigned char)((value >> 16) & 0xff); + buffer[2] = (unsigned char)((value >> 8) & 0xff); + buffer[3] = (unsigned char)((value ) & 0xff); +} + +#ifdef LODEPNG_COMPILE_ENCODER +static void LodePNG_add32bitInt(ucvector* buffer, unsigned value) +{ + ucvector_resize(buffer, buffer->size + 4); /*todo: give error if resize failed*/ + LodePNG_set32bitInt(&buffer->data[buffer->size - 4], value); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / File IO / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DISK + +unsigned LodePNG_loadFile(unsigned char** out, size_t* outsize, const char* filename) +{ + FILE* file; + long size; + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + + file = fopen(filename, "rb"); + if(!file) return 78; + + /*get filesize:*/ + fseek(file , 0 , SEEK_END); + size = ftell(file); + rewind(file); + + /*read contents of the file into the vector*/ + *outsize = 0; + *out = (unsigned char*)malloc((size_t)size); + if(size && (*out)) (*outsize) = fread(*out, 1, (size_t)size, file); + + fclose(file); + if(!(*out) && size) return 9900; /*the above malloc failed*/ + return 0; +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned LodePNG_saveFile(const unsigned char* buffer, size_t buffersize, const char* filename) +{ + FILE* file; + file = fopen(filename, "wb" ); + if(!file) return 79; + fwrite((char*)buffer , 1 , buffersize, file); + fclose(file); + return 0; +} + +#endif /*LODEPNG_COMPILE_DISK*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of common code and tools. Begin of Zlib related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ZLIB + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing single bits and bytes from/to stream for Deflate / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER +/*TODO: this ignores potential out of memory errors*/ +static void addBitToStream(size_t* bitpointer, ucvector* bitstream, unsigned char bit) +{ + /*add a new byte at the end*/ + if((*bitpointer) % 8 == 0) ucvector_push_back(bitstream, (unsigned char)0); + /*earlier bit of huffman code is in a lesser significant bit of an earlier byte*/ + (bitstream->data[bitstream->size - 1]) |= (bit << ((*bitpointer) & 0x7)); + (*bitpointer)++; +} + +static void addBitsToStream(size_t* bitpointer, ucvector* bitstream, unsigned value, size_t nbits) +{ + size_t i; + for(i = 0; i < nbits; i++) addBitToStream(bitpointer, bitstream, (unsigned char)((value >> i) & 1)); +} + +static void addBitsToStreamReversed(size_t* bitpointer, ucvector* bitstream, unsigned value, size_t nbits) +{ + size_t i; + for(i = 0; i < nbits; i++) addBitToStream(bitpointer, bitstream, (unsigned char)((value >> (nbits - 1 - i)) & 1)); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +#define READBIT(bitpointer, bitstream) ((bitstream[bitpointer >> 3] >> (bitpointer & 0x7)) & (unsigned char)1) + +static unsigned char readBitFromStream(size_t* bitpointer, const unsigned char* bitstream) +{ + unsigned char result = (unsigned char)(READBIT(*bitpointer, bitstream)); + (*bitpointer)++; + return result; +} + +static unsigned readBitsFromStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) +{ + unsigned result = 0, i; + for(i = 0; i < nbits; i++) + { + result += ((unsigned)READBIT(*bitpointer, bitstream)) << i; + (*bitpointer)++; + } + return result; +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflate - Huffman / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#define FIRST_LENGTH_CODE_INDEX 257 +#define LAST_LENGTH_CODE_INDEX 285 +/*256 literals, the end code, some length codes, and 2 unused codes*/ +#define NUM_DEFLATE_CODE_SYMBOLS 288 +/*the distance codes have their own symbols, 30 used, 2 unused*/ +#define NUM_DISTANCE_SYMBOLS 32 +/*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/ +#define NUM_CODE_LENGTH_CODES 19 + +/*the base lengths represented by codes 257-285*/ +static const unsigned LENGTHBASE[29] + = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, + 67, 83, 99, 115, 131, 163, 195, 227, 258}; + +/*the extra bits used by codes 257-285 (added to base length)*/ +static const unsigned LENGTHEXTRA[29] + = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 5, 5, 5, 5, 0}; + +/*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/ +static const unsigned DISTANCEBASE[30] + = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, + 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577}; + +/*the extra bits of backwards distances (added to base)*/ +static const unsigned DISTANCEEXTRA[30] + = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, + 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + +/*the order in which "code length alphabet code lengths" are stored, out of this +the huffman tree of the dynamic huffman tree lengths is generated*/ +static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES] + = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Huffman tree struct, containing multiple representations of the tree +*/ +typedef struct HuffmanTree +{ + uivector tree2d; + uivector tree1d; + uivector lengths; /*the lengths of the codes of the 1d-tree*/ + unsigned maxbitlen; /*maximum number of bits a single code can get*/ + unsigned numcodes; /*number of symbols in the alphabet = number of codes*/ +} HuffmanTree; + +/*function used for debug purposes to draw the tree in ascii art with C++*/ +/*#include +static void HuffmanTree_draw(HuffmanTree* tree) +{ + std::cout << "tree. length: " << tree->numcodes << " maxbitlen: " << tree->maxbitlen << std::endl; + for(size_t i = 0; i < tree->tree1d.size; i++) + { + if(tree->lengths.data[i]) + std::cout << i << " " << tree->tree1d.data[i] << " " << tree->lengths.data[i] << std::endl; + } + std::cout << std::endl; +}*/ + +static void HuffmanTree_init(HuffmanTree* tree) +{ + uivector_init(&tree->tree2d); + uivector_init(&tree->tree1d); + uivector_init(&tree->lengths); +} + +static void HuffmanTree_cleanup(HuffmanTree* tree) +{ + uivector_cleanup(&tree->tree2d); + uivector_cleanup(&tree->tree1d); + uivector_cleanup(&tree->lengths); +} + +/*the tree representation used by the decoder. return value is error*/ +static unsigned HuffmanTree_make2DTree(HuffmanTree* tree) +{ + unsigned nodefilled = 0; /*up to which node it is filled*/ + unsigned treepos = 0; /*position in the tree (1 of the numcodes columns)*/ + unsigned n, i; + + if(!uivector_resize(&tree->tree2d, tree->numcodes * 2)) return 9901; /*alloc fail*/ + + /* + convert tree1d[] to tree2d[][]. In the 2D array, a value of 32767 means + uninited, a value >= numcodes is an address to another bit, a value < numcodes + is a code. The 2 rows are the 2 possible bit values (0 or 1), there are as + many columns as codes - 1. + A good huffmann tree has N * 2 - 1 nodes, of which N - 1 are internal nodes. + Here, the internal nodes are stored (what their 0 and 1 option point to). + There is only memory for such good tree currently, if there are more nodes + (due to too long length codes), error 55 will happen + */ + for(n = 0; n < tree->numcodes * 2; n++) + { + tree->tree2d.data[n] = 32767; /*32767 here means the tree2d isn't filled there yet*/ + } + + for(n = 0; n < tree->numcodes; n++) /*the codes*/ + { + for(i = 0; i < tree->lengths.data[n]; i++) /*the bits for this code*/ + { + unsigned char bit = (unsigned char)((tree->tree1d.data[n] >> (tree->lengths.data[n] - i - 1)) & 1); + if(treepos > tree->numcodes - 2) return 55; /*error 55: oversubscribed; see description in header*/ + if(tree->tree2d.data[2 * treepos + bit] == 32767) /*not yet filled in*/ + { + if(i + 1 == tree->lengths.data[n]) /*last bit*/ + { + tree->tree2d.data[2 * treepos + bit] = n; /*put the current code in it*/ + treepos = 0; + } + else + { + /*put address of the next step in here, first that address has to be found of course + (it's just nodefilled + 1)...*/ + nodefilled++; + /*addresses encoded with numcodes added to it*/ + tree->tree2d.data[2 * treepos + bit] = nodefilled + tree->numcodes; + treepos = nodefilled; + } + } + else treepos = tree->tree2d.data[2 * treepos + bit] - tree->numcodes; + } + } + + for(n = 0; n < tree->numcodes * 2; n++) + { + if(tree->tree2d.data[n] == 32767) tree->tree2d.data[n] = 0; /*remove possible remaining 32767's*/ + } + + return 0; +} + +/* +Second step for the ...makeFromLengths and ...makeFromFrequencies functions. +numcodes, lengths and maxbitlen must already be filled in correctly. return +value is error. +*/ +static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) +{ + uivector blcount; + uivector nextcode; + unsigned bits, n, error = 0; + + uivector_init(&blcount); + uivector_init(&nextcode); + if(!uivector_resize(&tree->tree1d, tree->numcodes) + || !uivector_resizev(&blcount, tree->maxbitlen + 1, 0) + || !uivector_resizev(&nextcode, tree->maxbitlen + 1, 0)) + error = 9902; /*alloc fail*/ + + if(!error) + { + /*step 1: count number of instances of each code length*/ + for(bits = 0; bits < tree->numcodes; bits++) blcount.data[tree->lengths.data[bits]]++; + /*step 2: generate the nextcode values*/ + for(bits = 1; bits <= tree->maxbitlen; bits++) + { + nextcode.data[bits] = (nextcode.data[bits - 1] + blcount.data[bits - 1]) << 1; + } + /*step 3: generate all the codes*/ + for(n = 0; n < tree->numcodes; n++) + { + if(tree->lengths.data[n] != 0) tree->tree1d.data[n] = nextcode.data[tree->lengths.data[n]]++; + } + } + + uivector_cleanup(&blcount); + uivector_cleanup(&nextcode); + + if(!error) return HuffmanTree_make2DTree(tree); + else return error; +} + +/* +given the code lengths (as stored in the PNG file), generate the tree as defined +by Deflate. maxbitlen is the maximum bits that a code in the tree can have. +return value is error. +*/ +static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, + size_t numcodes, unsigned maxbitlen) +{ + unsigned i; + if(!uivector_resize(&tree->lengths, numcodes)) return 9903; /*alloc fail*/ + for(i = 0; i < numcodes; i++) tree->lengths.data[i] = bitlen[i]; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->maxbitlen = maxbitlen; + return HuffmanTree_makeFromLengths2(tree); +} + +#ifdef LODEPNG_COMPILE_ENCODER + +/* +A coin, this is the terminology used for the package-merge algorithm and the +coin collector's problem. This is used to generate the huffman tree. +A coin can be multiple coins (when they're merged) +*/ +typedef struct Coin +{ + uivector symbols; + float weight; /*the sum of all weights in this coin*/ +} Coin; + +static void Coin_init(Coin* c) +{ + uivector_init(&c->symbols); +} + +/*argument c is void* so that this dtor can be given as function pointer to the vector resize function*/ +static void Coin_cleanup(void* c) +{ + uivector_cleanup(&((Coin*)c)->symbols); +} + +static void Coin_copy(Coin* c1, const Coin* c2) +{ + c1->weight = c2->weight; + uivector_copy(&c1->symbols, &c2->symbols); +} + +static void addCoins(Coin* c1, const Coin* c2) +{ + size_t i; + for(i = 0; i < c2->symbols.size; i++) uivector_push_back(&c1->symbols, c2->symbols.data[i]); + c1->weight += c2->weight; +} + +/* +Coin_sort: This uses a simple combsort to sort the data. This function is not critical for +overall encoding speed and the data amount isn't that large. +*/ +static void Coin_sort(Coin* data, size_t amount) +{ + size_t gap = amount; + unsigned char swapped = 0; + while((gap > 1) || swapped) + { + size_t i; + gap = (gap * 10) / 13; /*shrink factor 1.3*/ + if(gap == 9 || gap == 10) gap = 11; /*combsort11*/ + if(gap < 1) gap = 1; + swapped = 0; + for(i = 0; i < amount - gap; i++) + { + size_t j = i + gap; + if(data[j].weight < data[i].weight) + { + float temp = data[j].weight; data[j].weight = data[i].weight; data[i].weight = temp; + uivector_swap(&data[i].symbols, &data[j].symbols); + swapped = 1; + } + } + } +} + +static unsigned HuffmanTree_fillInCoins(vector* coins, const unsigned* frequencies, unsigned numcodes, size_t sum) +{ + unsigned i; + for(i = 0; i < numcodes; i++) + { + Coin* coin; + if(frequencies[i] == 0) continue; /*it's important to exclude symbols that aren't present*/ + if(!vector_resize(coins, coins->size + 1)) + { + vector_cleanup(coins); + return 9904; /*alloc fail*/ + } + coin = (Coin*)(vector_get(coins, coins->size - 1)); + Coin_init(coin); + coin->weight = frequencies[i] / (float)sum; + uivector_push_back(&coin->symbols, i); + } + if(coins->size) Coin_sort((Coin*)coins->data, coins->size); + return 0; +} + +/*Create the Huffman tree given the symbol frequencies*/ +static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen) +{ + unsigned i, j; + size_t sum = 0, numpresent = 0; + unsigned error = 0; + + vector prev_row; /*type Coin, the previous row of coins*/ + vector coins; /*type Coin, the coins of the currently calculated row*/ + + tree->maxbitlen = maxbitlen; + + for(i = 0; i < numcodes; i++) + { + if(frequencies[i] > 0) + { + numpresent++; + sum += frequencies[i]; + } + } + + if(numcodes == 0) return 80; /*error: a tree of 0 symbols is not supposed to be made*/ + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + uivector_resize(&tree->lengths, 0); + if(!uivector_resizev(&tree->lengths, tree->numcodes, 0)) return 9905; /*alloc fail*/ + + /*there are no symbols at all, in that case add one symbol of value 0 to the tree (see RFC 1951 section 3.2.7) */ + if(numpresent == 0) + { + tree->lengths.data[0] = 1; + return HuffmanTree_makeFromLengths2(tree); + } + /*the package merge algorithm gives wrong results if there's only one symbol + (theoretically 0 bits would then suffice, but we need a proper symbol for zlib)*/ + else if(numpresent == 1) + { + for(i = 0; i < numcodes; i++) if(frequencies[i]) tree->lengths.data[i] = 1; + return HuffmanTree_makeFromLengths2(tree); + } + + vector_init(&coins, sizeof(Coin)); + vector_init(&prev_row, sizeof(Coin)); + + /*Package-Merge algorithm represented by coin collector's problem + For every symbol, maxbitlen coins will be created*/ + + /*first row, lowest denominator*/ + error = HuffmanTree_fillInCoins(&coins, frequencies, tree->numcodes, sum); + if(!error) + { + for(j = 1; j <= maxbitlen && !error; j++) /*each of the remaining rows*/ + { + vector_swap(&coins, &prev_row); /*swap instead of copying*/ + if(!vector_resized(&coins, 0, Coin_cleanup)) ERROR_BREAK(9906 /*alloc fail*/); + for(i = 0; i + 1 < prev_row.size; i += 2) + { + if(!vector_resize(&coins, coins.size + 1)) ERROR_BREAK(9907 /*alloc fail*/); + Coin_init((Coin*)vector_get(&coins, coins.size - 1)); + Coin_copy((Coin*)vector_get(&coins, coins.size - 1), (Coin*)vector_get(&prev_row, i)); + /*merge the coins into packages*/ + addCoins((Coin*)vector_get(&coins, coins.size - 1), (Coin*)vector_get(&prev_row, i + 1)); + } + if(j < maxbitlen) + { + error = HuffmanTree_fillInCoins(&coins, frequencies, tree->numcodes, sum); + } + } + } + + if(!error) + { + /*keep the coins with lowest weight, so that they add up to the amount of symbols - 1*/ + vector_resized(&coins, numpresent - 1, Coin_cleanup); + + /*calculate the lenghts of each symbol, as the amount of times a coin of each symbol is used*/ + for(i = 0; i < coins.size; i++) + { + Coin* coin = (Coin*)vector_get(&coins, i); + for(j = 0; j < coin->symbols.size; j++) tree->lengths.data[coin->symbols.data[j]]++; + } + + error = HuffmanTree_makeFromLengths2(tree); + } + + vector_cleanupd(&coins, Coin_cleanup); + vector_cleanupd(&prev_row, Coin_cleanup); + + return error; +} + +static unsigned HuffmanTree_getCode(const HuffmanTree* tree, unsigned index) +{ + return tree->tree1d.data[index]; +} + +static unsigned HuffmanTree_getLength(const HuffmanTree* tree, unsigned index) +{ + return tree->lengths.data[index]; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/ +static unsigned generateFixedLitLenTree(HuffmanTree* tree) +{ + unsigned i, error = 0; + uivector bitlen; + uivector_init(&bitlen); + if(!uivector_resize(&bitlen, NUM_DEFLATE_CODE_SYMBOLS)) error = 9909; /*alloc fail*/ + + if(!error) + { + /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/ + for(i = 0; i <= 143; i++) bitlen.data[i] = 8; + for(i = 144; i <= 255; i++) bitlen.data[i] = 9; + for(i = 256; i <= 279; i++) bitlen.data[i] = 7; + for(i = 280; i <= 287; i++) bitlen.data[i] = 8; + + error = HuffmanTree_makeFromLengths(tree, bitlen.data, NUM_DEFLATE_CODE_SYMBOLS, 15); + } + + uivector_cleanup(&bitlen); + return error; +} + +/*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static unsigned generateFixedDistanceTree(HuffmanTree* tree) +{ + unsigned i, error = 0; + uivector bitlen; + uivector_init(&bitlen); + if(!uivector_resize(&bitlen, NUM_DISTANCE_SYMBOLS)) error = 9910; /*alloc fail*/ + + /*there are 32 distance codes, but 30-31 are unused*/ + if(!error) + { + for(i = 0; i < NUM_DISTANCE_SYMBOLS; i++) bitlen.data[i] = 5; + error = HuffmanTree_makeFromLengths(tree, bitlen.data, NUM_DISTANCE_SYMBOLS, 15); + } + uivector_cleanup(&bitlen); + return error; +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* +returns the code, or (unsigned)(-1) if error happened +inbitlength is the length of the complete buffer, in bits (so its byte length times 8) +*/ +static unsigned huffmanDecodeSymbol(const unsigned char* in, size_t* bp, + const HuffmanTree* codetree, size_t inbitlength) +{ + unsigned treepos = 0, ct; + for(;;) + { + if(*bp > inbitlength) return (unsigned)(-1); /*error: end of input memory reached without endcode*/ + + /* + decode the symbol from the tree. The "readBitFromStream" code is inlined in + the expression below because this is the biggest bottleneck while decoding + */ + ct = codetree->tree2d.data[(treepos << 1) + READBIT(*bp, in)]; + (*bp)++; + if(ct < codetree->numcodes) return ct; /*the symbol is decoded, return it*/ + else treepos = ct - codetree->numcodes; /*symbol not yet decoded, instead move tree position*/ + + if(treepos >= codetree->numcodes) return (unsigned)(-1); /*error: it appeared outside the codetree*/ + } +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Inflator (Decompressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*get the tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static void getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) +{ + /*error checking not done, this is fixed stuff, it works, it doesn't depend on the image*/ + /*TODO: out of memory errors could still happen...*/ + generateFixedLitLenTree(tree_ll); + generateFixedDistanceTree(tree_d); +} + +/*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ +static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, + const unsigned char* in, size_t* bp, size_t inlength) +{ + /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/ + unsigned error = 0; + unsigned n, HLIT, HDIST, HCLEN, i; + size_t inbitlength = inlength * 8; + + /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/ + uivector bitlen_ll; /*lit,len code lengths*/ + uivector bitlen_d; /*dist code lengths*/ + /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/ + uivector bitlen_cl; + HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/ + + if((*bp) >> 3 >= inlength - 2) return 49; /*the bit pointer is or will go past the memory*/ + + /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/ + HLIT = readBitsFromStream(bp, in, 5) + 257; + /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/ + HDIST = readBitsFromStream(bp, in, 5) + 1; + /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/ + HCLEN = readBitsFromStream(bp, in, 4) + 4; + + HuffmanTree_init(&tree_cl); + uivector_init(&bitlen_ll); + uivector_init(&bitlen_d); + uivector_init(&bitlen_cl); + + while(!error) + { + /*read the code length codes out of 3 * (amount of code length codes) bits*/ + + if(!uivector_resize(&bitlen_cl, NUM_CODE_LENGTH_CODES)) ERROR_BREAK(9911); + + for(i = 0; i < NUM_CODE_LENGTH_CODES; i++) + { + if(i < HCLEN) bitlen_cl.data[CLCL_ORDER[i]] = readBitsFromStream(bp, in, 3); + else bitlen_cl.data[CLCL_ORDER[i]] = 0; /*if not, it must stay 0*/ + } + + error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl.data, bitlen_cl.size, 7); + if(error) break; + + /*now we can use this tree to read the lengths for the tree that this function will return*/ + uivector_resizev(&bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 0); + uivector_resizev(&bitlen_d, NUM_DISTANCE_SYMBOLS, 0); + i = 0; + if(!bitlen_ll.data || !bitlen_d.data) ERROR_BREAK(9912); /*alloc fail*/ + + /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/ + while(i < HLIT + HDIST) + { + unsigned code = huffmanDecodeSymbol(in, bp, &tree_cl, inbitlength); + if(code <= 15) /*a length code*/ + { + if(i < HLIT) bitlen_ll.data[i] = code; + else bitlen_d.data[i - HLIT] = code; + i++; + } + else if(code == 16) /*repeat previous*/ + { + unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/ + unsigned value; /*set value to the previous code*/ + + if((*bp) >> 3 >= inlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + + replength += readBitsFromStream(bp, in, 2); + + if((i - 1) < HLIT) value = bitlen_ll.data[i - 1]; + else value = bitlen_d.data[i - HLIT - 1]; + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; n++) + { + if(i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/ + if(i < HLIT) bitlen_ll.data[i] = value; + else bitlen_d.data[i - HLIT] = value; + i++; + } + } + else if(code == 17) /*repeat "0" 3-10 times*/ + { + unsigned replength = 3; /*read in the bits that indicate repeat length*/ + if((*bp) >> 3 >= inlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + + replength += readBitsFromStream(bp, in, 3); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; n++) + { + if(i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll.data[i] = 0; + else bitlen_d.data[i - HLIT] = 0; + i++; + } + } + else if(code == 18) /*repeat "0" 11-138 times*/ + { + unsigned replength = 11; /*read in the bits that indicate repeat length*/ + if((*bp) >> 3 >= inlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + + replength += readBitsFromStream(bp, in, 7); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; n++) + { + if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll.data[i] = 0; + else bitlen_d.data[i - HLIT] = 0; + i++; + } + } + else /*if(code == (unsigned)(-1))*/ /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ + { + if(code == (unsigned)(-1)) + { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + error = (*bp) > inlength * 8 ? 10 : 11; + } + else error = 16; /*unexisting code, this can never happen*/ + break; + } + } + if(error) break; + + if(bitlen_ll.data[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/ + + /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/ + error = HuffmanTree_makeFromLengths(tree_ll, &bitlen_ll.data[0], bitlen_ll.size, 15); + if(error) break; + error = HuffmanTree_makeFromLengths(tree_d, &bitlen_d.data[0], bitlen_d.size, 15); + + break; /*end of error-while*/ + } + + uivector_cleanup(&bitlen_cl); + uivector_cleanup(&bitlen_ll); + uivector_cleanup(&bitlen_d); + HuffmanTree_cleanup(&tree_cl); + + return error; +} + +/*inflate a block with dynamic of fixed Huffman tree*/ +static unsigned inflateHuffmanBlock(ucvector* out, const unsigned char* in, size_t* bp, + size_t* pos, size_t inlength, unsigned btype) +{ + unsigned error = 0; + HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/ + HuffmanTree tree_d; /*the huffman tree for distance codes*/ + size_t inbitlength = inlength * 8; + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + if(btype == 1) getTreeInflateFixed(&tree_ll, &tree_d); + else if(btype == 2) + { + error = getTreeInflateDynamic(&tree_ll, &tree_d, in, bp, inlength); + } + + for(;;) /*decode all symbols until end reached*/ + { + /*code_ll is literal, length or end code*/ + unsigned code_ll = huffmanDecodeSymbol(in, bp, &tree_ll, inbitlength); + if(code_ll <= 255) /*literal symbol*/ + { + if((*pos) >= out->size) + { + /*reserve more room at once*/ + if(!ucvector_resize(out, ((*pos) + 1) * 2)) ERROR_BREAK(9913 /*alloc fail*/); + } + out->data[(*pos)] = (unsigned char)(code_ll); + (*pos)++; + } + else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ + { + unsigned code_d, distance; + unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/ + size_t start, forward, backward, length; + + /*part 1: get length base*/ + length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX]; + + /*part 2: get extra bits and add the value of that to length*/ + numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX]; + if(((*bp) >> 3) >= inlength) ERROR_BREAK(51); /*error, bit pointer will jump past memory*/ + length += readBitsFromStream(bp, in, numextrabits_l); + + /*part 3: get distance code*/ + code_d = huffmanDecodeSymbol(in, bp, &tree_d, inbitlength); + if(code_d > 29) + { + if(code_ll == (unsigned)(-1)) /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ + { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + error = (*bp) > inlength * 8 ? 10 : 11; + } + else error = 18; /*error: invalid distance code (30-31 are never used)*/ + break; + } + distance = DISTANCEBASE[code_d]; + + /*part 4: get extra bits from distance*/ + numextrabits_d = DISTANCEEXTRA[code_d]; + if(((*bp) >> 3) >= inlength) ERROR_BREAK(51); /*error, bit pointer will jump past memory*/ + + distance += readBitsFromStream(bp, in, numextrabits_d); + + /*part 5: fill in all the out[n] values based on the length and dist*/ + start = (*pos); + backward = start - distance; + if((*pos) + length >= out->size) + { + /*reserve more room at once*/ + if(!ucvector_resize(out, ((*pos) + length) * 2)) ERROR_BREAK(9914 /*alloc fail*/); + } + + for(forward = 0; forward < length; forward++) + { + out->data[(*pos)] = out->data[backward]; + (*pos)++; + backward++; + if(backward >= start) backward = start - distance; + } + } + else if(code_ll == 256) + { + break; /*end code, break the loop*/ + } + else /*if(code == (unsigned)(-1))*/ /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ + { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + error = (*bp) > inlength * 8 ? 10 : 11; + break; + } + } + + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned inflateNoCompression(ucvector* out, const unsigned char* in, size_t* bp, size_t* pos, size_t inlength) +{ + /*go to first boundary of byte*/ + size_t p; + unsigned LEN, NLEN, n, error = 0; + while(((*bp) & 0x7) != 0) (*bp)++; + p = (*bp) / 8; /*byte position*/ + + /*read LEN (2 bytes) and NLEN (2 bytes)*/ + if(p >= inlength - 4) return 52; /*error, bit pointer will jump past memory*/ + LEN = in[p] + 256 * in[p + 1]; p += 2; + NLEN = in[p] + 256 * in[p + 1]; p += 2; + + /*check if 16-bit NLEN is really the one's complement of LEN*/ + if(LEN + NLEN != 65535) return 21; /*error: NLEN is not one's complement of LEN*/ + + if((*pos) + LEN >= out->size) + { + if(!ucvector_resize(out, (*pos) + LEN)) return 9915; /*alloc fail*/ + } + + /*read the literal data: LEN bytes are now stored in the out buffer*/ + if(p + LEN > inlength) return 23; /*error: reading outside of in buffer*/ + for(n = 0; n < LEN; n++) out->data[(*pos)++] = in[p++]; + + (*bp) = p * 8; + + return error; +} + +/*inflate the deflated data (cfr. deflate spec); return value is the error*/ +static unsigned LodePNG_inflate(ucvector* out, const unsigned char* in, size_t insize, size_t inpos) +{ + /*bit pointer in the "in" data, current byte is bp >> 3, current bit is bp & 0x7 (from lsb to msb of the byte)*/ + size_t bp = 0; + unsigned BFINAL = 0; + size_t pos = 0; /*byte position in the out buffer*/ + + unsigned error = 0; + + while(!BFINAL) + { + unsigned BTYPE; + if(bp + 2 >= insize * 8) return 52; /*error, bit pointer will jump past memory*/ + BFINAL = readBitFromStream(&bp, &in[inpos]); + BTYPE = 1 * readBitFromStream(&bp, &in[inpos]); + BTYPE += 2 * readBitFromStream(&bp, &in[inpos]); + + if(BTYPE == 3) return 20; /*error: invalid BTYPE*/ + else if(BTYPE == 0) error = inflateNoCompression(out, &in[inpos], &bp, &pos, insize); /*no compression*/ + else error = inflateHuffmanBlock(out, &in[inpos], &bp, &pos, insize, BTYPE); /*compression, BTYPE 01 or 10*/ + + if(error) return error; + } + + /*Only now we know the true size of out, resize it to that*/ + if(!ucvector_resize(out, pos)) error = 9916; /*alloc fail*/ + + return error; +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflator (Compressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static const size_t MAX_SUPPORTED_DEFLATE_LENGTH = 258; + +/*bitlen is the size in bits of the code*/ +static void addHuffmanSymbol(size_t* bp, ucvector* compressed, unsigned code, unsigned bitlen) +{ + addBitsToStreamReversed(bp, compressed, code, bitlen); +} + +/*search the index in the array, that has the largest value smaller than or equal to the given value, +given array must be sorted (if no value is smaller, it returns the size of the given array)*/ +static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) +{ + /*linear search implementation*/ + /*for(size_t i = 1; i < array_size; i++) if(array[i] > value) return i - 1; + return array_size - 1;*/ + + /*binary search implementation (not that much faster) (precondition: array_size > 0)*/ + size_t left = 1; + size_t right = array_size - 1; + while(left <= right) + { + size_t mid = (left + right) / 2; + if(array[mid] <= value) left = mid + 1; /*the value to find is more to the right*/ + else if(array[mid - 1] > value) right = mid - 1; /*the value to find is more to the left*/ + else return mid - 1; + } + return array_size - 1; +} + +static void addLengthDistance(uivector* values, size_t length, size_t distance) +{ + /*values in encoded vector are those used by deflate: + 0-255: literal bytes + 256: end + 257-285: length/distance pair (length code, followed by extra length bits, distance code, extra distance bits) + 286-287: invalid*/ + + unsigned length_code = (unsigned)searchCodeIndex(LENGTHBASE, 29, length); + unsigned extra_length = (unsigned)(length - LENGTHBASE[length_code]); + unsigned dist_code = (unsigned)searchCodeIndex(DISTANCEBASE, 30, distance); + unsigned extra_distance = (unsigned)(distance - DISTANCEBASE[dist_code]); + + uivector_push_back(values, length_code + FIRST_LENGTH_CODE_INDEX); + uivector_push_back(values, extra_length); + uivector_push_back(values, dist_code); + uivector_push_back(values, extra_distance); +} + +#if 0 +/*the "brute force" version of the encodeLZ7 algorithm, not used anymore, kept here for reference*/ +static unsigned encodeLZ77_brute(uivector* out, const unsigned char* in, size_t insize, unsigned windowSize) +{ + size_t pos; + for(pos = 0; pos < insize; pos++) + { + size_t length = 0, offset = 0; /*the length and offset found for the current position*/ + size_t max_offset = pos < windowSize ? pos : windowSize; /*how far back to test*/ + size_t current_offset; + + /**search for the longest string, backwards through all possible distances (=offsets)**/ + for(current_offset = 1; current_offset < max_offset; current_offset++) + { + size_t backpos = pos - current_offset; + if(in[backpos] == in[pos]) + { + /*test the next characters*/ + size_t current_length = 1; + size_t backtest = backpos + 1; + size_t foretest = pos + 1; + /*maximum supporte length by deflate is max length*/ + while(foretest < insize && in[backtest] == in[foretest] && current_length < MAX_SUPPORTED_DEFLATE_LENGTH) + { + if(backpos >= pos) + { + /*continue as if we work on the decoded bytes after pos by jumping back before pos*/ + backpos -= current_offset; + } + current_length++; + backtest++; + foretest++; + } + if(current_length > length) + { + length = current_length; /*the longest length*/ + offset = current_offset; /*the offset that is related to this longest length*/ + /*you can jump out of this for loop once a length of max length is found (gives significant speed gain)*/ + if(current_length == MAX_SUPPORTED_DEFLATE_LENGTH) break; + } + } + } + + /**encode it as length/distance pair or literal value**/ + if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ + { + uivector_push_back(out, in[pos]); + } + else + { + addLengthDistance(out, length, offset); + pos += (length - 1); + } + } /*end of the loop through each character of input*/ + return 0; +} +#endif + +static const unsigned HASH_NUM_VALUES = 2048; +static const unsigned HASH_NUM_CHARACTERS = 3; +static const unsigned HASH_SHIFT = 2; +/* +The HASH_NUM_CHARACTERS value is used to make encoding faster by using longer +sequences to generate a hash value from the stream bytes. Setting it to 3 +gives exactly the same compression as the brute force method, since deflate's +run length encoding starts with lengths of 3. Setting it to higher values, +like 6, can make the encoding faster (not always though!), but will cause the +encoding to miss any length between 3 and this value, so that the compression +may be worse (but this can vary too depending on the image, sometimes it is +even a bit better instead). +The HASH_NUM_VALUES is the amount of unique possible hash values that +combinations of bytes can give, the higher it is the more memory is needed, but +if it's too low the advantage of hashing is gone. +*/ + +static unsigned getHash(const unsigned char* data, size_t size, size_t pos, size_t num) +{ + unsigned result = 0; + size_t amount, i; + if(pos >= size) return 0; + amount = num; + if(pos + amount >= size) amount = size - pos; + for(i = 0; i < amount; i++) result ^= (data[pos + i] << (i * HASH_SHIFT)); + return result % HASH_NUM_VALUES; +} + +static unsigned countInitialZeros(const unsigned char* data, size_t size, size_t pos) +{ + size_t max_count = MAX_SUPPORTED_DEFLATE_LENGTH; + size_t i; + if(max_count > size - pos) max_count = size - pos; + for(i = 0; i < max_count; i++) + { + if(data[pos + i] != 0) + return i; + } + return max_count; +} + +/*push a value to the vector in a circular way. This is to do as if we're extending +the vector's size forever, but instead the size is limited to maxsize and it wraps +around, to avoid too large memory size. The pos pointer gets updated to the current +end (unless updatepos is false, in that case pos is only used to know the current +value). returns 1 on success, 0 if fail*/ +static unsigned push_circular(uivector* v, unsigned* pos, unsigned value, size_t maxsize, unsigned updatepos) +{ + if(v->size < maxsize) + { + if(!uivector_push_back(v, value)) return 0; + if(updatepos) (*pos)++; + } + else + { + if(updatepos) + { + (*pos)++; + if((*pos) > maxsize) (*pos) = 1; + } + v->data[(*pos) - 1] = value; + } + return 1; +} + +/*Enable to use lazy instead of greedy matghing. It looks one byte further +to see if that one gives a longer distance. This gives slightly better compression, at the cost +of a speed loss.*/ +#define LAZY_MATCHING + +/* +LZ77-encode the data. Return value is error code. The input are raw bytes, the output +is in the form of unsigned integers with codes representing for example literal bytes, or +length/distance pairs. +It uses a hash table technique to let it encode faster. When doing LZ77 encoding, a +sliding window (of windowSize) is used, and all past bytes in that window can be used as +the "dictionary". A brute force search through all possible distances would be slow, and +this hash technique is one out of several ways to speed this up. +*/ +static unsigned encodeLZ77(uivector* out, const unsigned char* in, size_t insize, unsigned windowSize) +{ + /**generate hash table**/ + /* + The hash table is 2-dimensional. For every possible hash value, it contains a list of positions + in the data where this hash occured. + tablepos1 and tablepos2 remember the last used start and end index in the hash table for each hash value. + */ + vector table; /*HASH_NUM_VALUES uivectors; this is what would be an std::vector > in C++*/ + uivector tablepos1, tablepos2; + /*hash 0 indicates a possible common case of a long sequence of zeros, store and use the amount here for a speedup*/ + uivector initialZerosTable; + unsigned pos, i, error = 0; + unsigned hash_num_characters = HASH_NUM_CHARACTERS; + + vector_init(&table, sizeof(uivector)); + if(!vector_resize(&table, HASH_NUM_VALUES)) return 9917; /*alloc fail*/ + for(i = 0; i < HASH_NUM_VALUES; i++) + { + uivector* v = (uivector*)vector_get(&table, i); + uivector_init(v); + } + + /*remember start and end positions in the tables to search in*/ + uivector_init(&tablepos1); + uivector_init(&tablepos2); + uivector_init(&initialZerosTable); + + if(!uivector_resizev(&tablepos1, HASH_NUM_VALUES, 0)) error = 9918; /*alloc fail*/ + if(!uivector_resizev(&tablepos2, HASH_NUM_VALUES, 0)) error = 9919; /*alloc fail*/ + + if(!error) + { + unsigned offset, max_offset; /*the offset represents the distance in LZ77 terminology*/ + unsigned length, tablepos; +#ifdef LAZY_MATCHING + unsigned lazy = 0; + unsigned lazylength, lazyoffset; +#endif /*LAZY_MATCHING*/ + unsigned hash, initialZeros = 0; + unsigned backpos, current_offset, t1, t2, t11, current_length; + const unsigned char *lastptr, *foreptr, *backptr; + uivector* v; /*vector from the hash table we're currently working on*/ + unsigned hashWindow = windowSize; + unsigned numones = 0; + + for(pos = 0; pos < insize; pos++) + { + length = 0, offset = 0; /*the length and offset found for the current position*/ + max_offset = pos < windowSize ? pos : windowSize; /*how far back to test*/ + + /*search for the longest string. First find out where in the table to start + (the first value that is in the range from "pos - max_offset" to "pos")*/ + hash = getHash(in, insize, pos, hash_num_characters); + v = (uivector*)vector_get(&table, hash); + if(!push_circular(v, &tablepos2.data[hash], pos, hashWindow, 1)) ERROR_BREAK(9920 /*alloc fail*/); + + if(hash == 0) + { + initialZeros = countInitialZeros(in, insize, pos); + if(!push_circular(&initialZerosTable, &tablepos2.data[hash], initialZeros, hashWindow, 0)) + ERROR_BREAK(9920 /*alloc fail*/); + } + + while(v->data[tablepos1.data[hash]] < pos - max_offset) + { + /*it now points to the first value in the table for which the index is + larger than or equal to pos - max_offset*/ + tablepos1.data[hash]++; + if(tablepos1.data[hash] >= hashWindow) tablepos1.data[hash] = 0; + } + + t1 = tablepos1.data[hash]; + t2 = tablepos2.data[hash] - 1; + if(tablepos2.data[hash] == 0) t2 = hashWindow - 1; + + lastptr = &in[insize < pos + MAX_SUPPORTED_DEFLATE_LENGTH ? insize : pos + MAX_SUPPORTED_DEFLATE_LENGTH]; + + t11 = t1 == 0 ? hashWindow - 1 : t1 - 1; + for(tablepos = t2 == 0 ? hashWindow - 1 : t2 - 1; + tablepos != t2 && tablepos != t11 && tablepos < v->size; + tablepos = tablepos == 0 ? hashWindow - 1 : tablepos - 1) + { + backpos = v->data[tablepos]; + current_offset = pos - backpos; + /*test the next characters*/ + foreptr = &in[pos]; + backptr = &in[backpos]; + + if(hash == 0) + { + unsigned skip = initialZerosTable.data[tablepos]; + if(skip > initialZeros) skip = initialZeros; + if(skip > insize - pos) skip = insize - pos; + backptr += skip; + foreptr += skip; + } + while(foreptr != lastptr && *backptr == *foreptr) /*maximum supported length by deflate is max length*/ + { + ++backptr; + ++foreptr; + } + current_length = (unsigned)(foreptr - &in[pos]); + if(current_length > length) + { + length = current_length; /*the longest length*/ + offset = current_offset; /*the offset that is related to this longest length*/ + /*you can jump out of this for loop once a length of max length is found (gives significant speed gain)*/ + if(current_length == MAX_SUPPORTED_DEFLATE_LENGTH) break; + } + } + +#ifdef LAZY_MATCHING + if(!lazy && length >= 3 && length < MAX_SUPPORTED_DEFLATE_LENGTH) + { + lazylength = length; + lazyoffset = offset; + lazy = 1; + continue; + } + if(lazy) + { + if(pos == 0) ERROR_BREAK(81); + lazy = 0; + if(length > lazylength + 1) + { + /*push the previous character as literal*/ + if(!uivector_push_back(out, in[pos - 1])) ERROR_BREAK(9921 /*alloc fail*/); + } + else + { + length = lazylength; + offset = lazyoffset; + pos--; + } + } +#endif /*LAZY_MATCHING*/ + + /**encode it as length/distance pair or literal value**/ + if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ + { + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(9921 /*alloc fail*/); + } + else + { + unsigned j, local_hash; + addLengthDistance(out, length, offset); + for(j = 0; j < length - 1; j++) + { + unsigned* t2p; /*pointer to current tablepos2 element*/ + pos++; + local_hash = getHash(in, insize, pos, hash_num_characters); + t2p = &tablepos2.data[local_hash]; + v = (uivector*)vector_get(&table, local_hash); + if(!push_circular(v, t2p, pos, hashWindow, 1)) ERROR_BREAK(9920 /*alloc fail*/); + + if(local_hash == 0) + { + initialZeros = countInitialZeros(in, insize, pos); + if(!push_circular(&initialZerosTable, t2p, initialZeros, hashWindow, 0)) + ERROR_BREAK(9922 /*alloc fail*/); + } + if(local_hash == 1 && hash_num_characters == 3) + { + /* + If many hash values are getting grouped together in hash value 1, 4, 16, 20, ..., + it indicates there are many near-zero values. This is not zero enough to benefit from a speed + increase from the initialZerosTable, and makes it very slow. For that case only, switch to + hash_num_characters = 6. Value 6 is experimentally found to be fastest. For this particular type + of file, the compression isn't even worse, despite the fact that lengths < 6 are now no longer + found, and that by changing hash_num_characters not all previously found hash values are still valid. + Almost all images compress fast enough and smaller with hash_num_characters = 3, except sine plasma + images. Those benefit a lot from this heuristic. + */ + if(numones > 8192 && numones > pos / 16) hash_num_characters = 6; + else numones++; + } + } + } + } /*end of the loop through each character of input*/ + } /*end of "if(!error)"*/ + + /*cleanup*/ + for(i = 0; i < table.size; i++) + { + uivector* v = (uivector*)vector_get(&table, i); + uivector_cleanup(v); + } + vector_cleanup(&table); + uivector_cleanup(&tablepos1); + uivector_cleanup(&tablepos2); + uivector_cleanup(&initialZerosTable); + return error; +} + +/* /////////////////////////////////////////////////////////////////////////// */ + +static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) +{ + /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte, + 2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/ + + size_t i, j, numdeflateblocks = datasize / 65536 + 1; + unsigned datapos = 0; + for(i = 0; i < numdeflateblocks; i++) + { + unsigned BFINAL, BTYPE, LEN, NLEN; + unsigned char firstbyte; + + BFINAL = (i == numdeflateblocks - 1); + BTYPE = 0; + + firstbyte = (unsigned char)(BFINAL + ((BTYPE & 1) << 1) + ((BTYPE & 2) << 1)); + ucvector_push_back(out, firstbyte); + + LEN = 65535; + if(datasize - datapos < 65535) LEN = (unsigned)datasize - datapos; + NLEN = 65535 - LEN; + + ucvector_push_back(out, (unsigned char)(LEN % 256)); + ucvector_push_back(out, (unsigned char)(LEN / 256)); + ucvector_push_back(out, (unsigned char)(NLEN % 256)); + ucvector_push_back(out, (unsigned char)(NLEN / 256)); + + /*Decompressed data*/ + for(j = 0; j < 65535 && datapos < datasize; j++) + { + ucvector_push_back(out, data[datapos++]); + } + } + + return 0; +} + +/* +write the lz77-encoded data, which has lit, len and dist codes, to compressed stream using huffman trees. +tree_ll: the tree for lit and len codes. +tree_d: the tree for distance codes. +*/ +static void writeLZ77data(size_t* bp, ucvector* out, const uivector* lz77_encoded, + const HuffmanTree* tree_ll, const HuffmanTree* tree_d) +{ + size_t i = 0; + for(i = 0; i < lz77_encoded->size; i++) + { + unsigned val = lz77_encoded->data[i]; + addHuffmanSymbol(bp, out, HuffmanTree_getCode(tree_ll, val), HuffmanTree_getLength(tree_ll, val)); + if(val > 256) /*for a length code, 3 more things have to be added*/ + { + unsigned length_index = val - FIRST_LENGTH_CODE_INDEX; + unsigned n_length_extra_bits = LENGTHEXTRA[length_index]; + unsigned length_extra_bits = lz77_encoded->data[++i]; + + unsigned distance_code = lz77_encoded->data[++i]; + + unsigned distance_index = distance_code; + unsigned n_distance_extra_bits = DISTANCEEXTRA[distance_index]; + unsigned distance_extra_bits = lz77_encoded->data[++i]; + + addBitsToStream(bp, out, length_extra_bits, n_length_extra_bits); + addHuffmanSymbol(bp, out, HuffmanTree_getCode(tree_d, distance_code), + HuffmanTree_getLength(tree_d, distance_code)); + addBitsToStream(bp, out, distance_extra_bits, n_distance_extra_bits); + } + } +} + +/*Deflate for a block of type "dynamic", that is, with freely, optimally, created huffman trees*/ +static unsigned deflateDynamic(ucvector* out, const unsigned char* data, size_t datasize, + const LodePNG_CompressSettings* settings) +{ + unsigned error = 0; + + /* + A block is compressed as follows: The PNG data is lz77 encoded, resulting in + literal bytes and length/distance pairs. This is then huffman compressed with + two huffman trees. One huffman tree is used for the lit and len values ("ll"), + another huffman tree is used for the dist values ("d"). These two trees are + stored using their code lengths, and to compress even more these code lengths + are also run-length encoded and huffman compressed. This gives a huffman tree + of code lengths "cl". The code lenghts used to describe this third tree are + the code length code lengths ("clcl"). + */ + + /*The lz77 encoded data, represented with integers since there will also be length and distance codes in it*/ + uivector lz77_encoded; + HuffmanTree tree_ll; /*tree for lit,len values*/ + HuffmanTree tree_d; /*tree for distance codes*/ + HuffmanTree tree_cl; /*tree for encoding the code lengths representing tree_ll and tree_d*/ + uivector frequencies_ll; /*frequency of lit,len codes*/ + uivector frequencies_d; /*frequency of dist codes*/ + uivector frequencies_cl; /*frequency of code length codes*/ + uivector bitlen_lld; /*lit,len,dist code lenghts (int bits), literally (without repeat codes).*/ + uivector bitlen_lld_e; /*bitlen_lld encoded with repeat codes (this is a rudemtary run length compression)*/ + /*bitlen_cl is the code length code lengths ("clcl"). The bit lengths of codes to represent tree_cl + (these are written as is in the file, it would be crazy to compress these using yet another huffman + tree that needs to be represented by yet another set of code lengths)*/ + uivector bitlen_cl; + + /* + Due to the huffman compression of huffman tree representations ("two levels"), there are some anologies: + bitlen_lld is to tree_cl what data is to tree_ll and tree_d. + bitlen_lld_e is to bitlen_lld what lz77_encoded is to data. + bitlen_cl is to bitlen_lld_e what bitlen_lld is to lz77_encoded. + */ + + unsigned BFINAL = 1; /*make only one block... the first and final one*/ + size_t numcodes_ll, numcodes_d, i; + size_t bp = 0; /*the bit pointer*/ + unsigned HLIT, HDIST, HCLEN; + + uivector_init(&lz77_encoded); + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + HuffmanTree_init(&tree_cl); + uivector_init(&frequencies_ll); + uivector_init(&frequencies_d); + uivector_init(&frequencies_cl); + uivector_init(&bitlen_lld); + uivector_init(&bitlen_lld_e); + uivector_init(&bitlen_cl); + + /*This while loop is never loops due to a break at the end, it is here to + allow breaking out of it to the cleanup phase on error conditions.*/ + while(!error) + { + if(settings->useLZ77) + { + error = encodeLZ77(&lz77_encoded, data, datasize, settings->windowSize); /*LZ77 encoded*/ + if(error) break; + } + else + { + if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(9923 /*alloc fail*/); + for(i = 0; i < datasize; i++) lz77_encoded.data[i] = data[i]; /*no LZ77, but still will be Huffman compressed*/ + } + + if(!uivector_resizev(&frequencies_ll, 286, 0)) ERROR_BREAK(9924 /*alloc fail*/); + if(!uivector_resizev(&frequencies_d, 30, 0)) ERROR_BREAK(9925 /*alloc fail*/); + + /*Count the frequencies of lit, len and dist codes*/ + for(i = 0; i < lz77_encoded.size; i++) + { + unsigned symbol = lz77_encoded.data[i]; + frequencies_ll.data[symbol]++; + if(symbol > 256) + { + unsigned dist = lz77_encoded.data[i + 2]; + frequencies_d.data[dist]++; + i += 3; + } + } + frequencies_ll.data[256] = 1; /*there will be exactly 1 end code, at the end of the block*/ + + /*Make both huffman trees, one for the lit and len codes, one for the dist codes*/ + error = HuffmanTree_makeFromFrequencies(&tree_ll, frequencies_ll.data, frequencies_ll.size, 15); + if(error) break; + error = HuffmanTree_makeFromFrequencies(&tree_d, frequencies_d.data, frequencies_d.size, 15); + if(error) break; + + numcodes_ll = tree_ll.numcodes; if(numcodes_ll > 286) numcodes_ll = 286; + numcodes_d = tree_d.numcodes; if(numcodes_d > 30) numcodes_d = 30; + /*store the code lengths of both generated trees in bitlen_lld*/ + for(i = 0; i < numcodes_ll; i++) uivector_push_back(&bitlen_lld, HuffmanTree_getLength(&tree_ll, (unsigned)i)); + for(i = 0; i < numcodes_d; i++) uivector_push_back(&bitlen_lld, HuffmanTree_getLength(&tree_d, (unsigned)i)); + + /*run-length compress bitlen_ldd into bitlen_lld_e by using repeat codes 16 (copy length 3-6 times), + 17 (3-10 zeroes), 18 (11-138 zeroes)*/ + for(i = 0; i < (unsigned)bitlen_lld.size; i++) + { + unsigned j = 0; /*amount of repititions*/ + while(i + j + 1 < (unsigned)bitlen_lld.size && bitlen_lld.data[i + j + 1] == bitlen_lld.data[i]) j++; + + if(bitlen_lld.data[i] == 0 && j >= 2) /*repeat code for zeroes*/ + { + j++; /*include the first zero*/ + if(j <= 10) /*repeat code 17 supports max 10 zeroes*/ + { + uivector_push_back(&bitlen_lld_e, 17); + uivector_push_back(&bitlen_lld_e, j - 3); + } + else /*repeat code 18 supports max 138 zeroes*/ + { + if(j > 138) j = 138; + uivector_push_back(&bitlen_lld_e, 18); + uivector_push_back(&bitlen_lld_e, j - 11); + } + i += (j - 1); + } + else if(j >= 3) /*repeat code for value other than zero*/ + { + size_t k; + unsigned num = j / 6, rest = j % 6; + uivector_push_back(&bitlen_lld_e, bitlen_lld.data[i]); + for(k = 0; k < num; k++) + { + uivector_push_back(&bitlen_lld_e, 16); + uivector_push_back(&bitlen_lld_e, 6 - 3); + } + if(rest >= 3) + { + uivector_push_back(&bitlen_lld_e, 16); + uivector_push_back(&bitlen_lld_e, rest - 3); + } + else j -= rest; + i += j; + } + else /*too short to benefit from repeat code*/ + { + uivector_push_back(&bitlen_lld_e, bitlen_lld.data[i]); + } + } + + /*generate tree_cl, the huffmantree of huffmantrees*/ + + if(!uivector_resizev(&frequencies_cl, NUM_CODE_LENGTH_CODES, 0)) ERROR_BREAK(9926 /*alloc fail*/); + for(i = 0; i < bitlen_lld_e.size; i++) + { + frequencies_cl.data[bitlen_lld_e.data[i]]++; + /*after a repeat code come the bits that specify the number of repetitions, + those don't need to be in the frequencies_cl calculation*/ + if(bitlen_lld_e.data[i] >= 16) i++; + } + + error = HuffmanTree_makeFromFrequencies(&tree_cl, frequencies_cl.data, frequencies_cl.size, 7); + if(error) break; + + if(!uivector_resize(&bitlen_cl, NUM_CODE_LENGTH_CODES)) ERROR_BREAK(9927 /*alloc fail*/); + for(i = 0; i < NUM_CODE_LENGTH_CODES; i++) + { + /*lenghts of code length tree is in the order as specified by deflate*/ + bitlen_cl.data[i] = HuffmanTree_getLength(&tree_cl, CLCL_ORDER[i]); + } + while(bitlen_cl.data[bitlen_cl.size - 1] == 0 && bitlen_cl.size > 4) + { + /*remove zeros at the end, but minimum size must be 4*/ + if(!uivector_resize(&bitlen_cl, bitlen_cl.size - 1)) ERROR_BREAK(9928 /*alloc fail*/); + } + if(error) break; + + /* + Write everything into the output + + After the BFINAL and BTYPE, the dynamic block consists out of the following: + - 5 bits HLIT, 5 bits HDIST, 4 bits HCLEN + - (HCLEN+4)*3 bits code lengths of code length alphabet + - HLIT + 257 code lenghts of lit/length alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - HDIST + 1 code lengths of distance alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - compressed data + - 256 (end code) + */ + + /*Write block type*/ + addBitToStream(&bp, out, BFINAL); + addBitToStream(&bp, out, 0); /*first bit of BTYPE "dynamic"*/ + addBitToStream(&bp, out, 1); /*second bit of BTYPE "dynamic"*/ + + /*write the HLIT, HDIST and HCLEN values*/ + HLIT = (unsigned)(numcodes_ll - 257); + HDIST = (unsigned)(numcodes_d - 1); + HCLEN = (unsigned)bitlen_cl.size - 4; + addBitsToStream(&bp, out, HLIT, 5); + addBitsToStream(&bp, out, HDIST, 5); + addBitsToStream(&bp, out, HCLEN, 4); + + /*write the code lenghts of the code length alphabet*/ + for(i = 0; i < HCLEN + 4; i++) addBitsToStream(&bp, out, bitlen_cl.data[i], 3); + + /*write the lenghts of the lit/len AND the dist alphabet*/ + for(i = 0; i < bitlen_lld_e.size; i++) + { + addHuffmanSymbol(&bp, out, HuffmanTree_getCode(&tree_cl, bitlen_lld_e.data[i]), + HuffmanTree_getLength(&tree_cl, bitlen_lld_e.data[i])); + /*extra bits of repeat codes*/ + if(bitlen_lld_e.data[i] == 16) addBitsToStream(&bp, out, bitlen_lld_e.data[++i], 2); + else if(bitlen_lld_e.data[i] == 17) addBitsToStream(&bp, out, bitlen_lld_e.data[++i], 3); + else if(bitlen_lld_e.data[i] == 18) addBitsToStream(&bp, out, bitlen_lld_e.data[++i], 7); + } + + /*write the compressed data symbols*/ + writeLZ77data(&bp, out, &lz77_encoded, &tree_ll, &tree_d); + /*error: the length of the end code 256 must be larger than 0*/ + if(HuffmanTree_getLength(&tree_ll, 256) == 0) ERROR_BREAK(64); + + /*write the end code*/ + addHuffmanSymbol(&bp, out, HuffmanTree_getCode(&tree_ll, 256), HuffmanTree_getLength(&tree_ll, 256)); + + break; /*end of error-while*/ + } + + /*cleanup*/ + uivector_cleanup(&lz77_encoded); + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + HuffmanTree_cleanup(&tree_cl); + uivector_cleanup(&frequencies_ll); + uivector_cleanup(&frequencies_d); + uivector_cleanup(&frequencies_cl); + uivector_cleanup(&bitlen_lld_e); + uivector_cleanup(&bitlen_lld); + uivector_cleanup(&bitlen_cl); + + return error; +} + +static unsigned deflateFixed(ucvector* out, const unsigned char* data, + size_t datasize, const LodePNG_CompressSettings* settings) +{ + HuffmanTree tree_ll; /*tree for literal values and length codes*/ + HuffmanTree tree_d; /*tree for distance codes*/ + + unsigned BFINAL = 1; /*make only one block... the first and final one*/ + unsigned error = 0; + size_t i, bp = 0; /*the bit pointer*/ + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + generateFixedLitLenTree(&tree_ll); + generateFixedDistanceTree(&tree_d); + + addBitToStream(&bp, out, BFINAL); + addBitToStream(&bp, out, 1); /*first bit of BTYPE*/ + addBitToStream(&bp, out, 0); /*second bit of BTYPE*/ + + if(settings->useLZ77) /*LZ77 encoded*/ + { + uivector lz77_encoded; + uivector_init(&lz77_encoded); + error = encodeLZ77(&lz77_encoded, data, datasize, settings->windowSize); + if(!error) writeLZ77data(&bp, out, &lz77_encoded, &tree_ll, &tree_d); + uivector_cleanup(&lz77_encoded); + } + else /*no LZ77, but still will be Huffman compressed*/ + { + for(i = 0; i < datasize; i++) + { + addHuffmanSymbol(&bp, out, HuffmanTree_getCode(&tree_ll, data[i]), HuffmanTree_getLength(&tree_ll, data[i])); + } + } + /*add END code*/ + if(!error) addHuffmanSymbol(&bp, out, HuffmanTree_getCode(&tree_ll, 256), HuffmanTree_getLength(&tree_ll, 256)); + + /*cleanup*/ + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned LodePNG_deflate(ucvector* out, const unsigned char* data, size_t datasize, + const LodePNG_CompressSettings* settings) +{ + unsigned error = 0; + if(settings->btype == 0) error = deflateNoCompression(out, data, datasize); + else if(settings->btype == 1) error = deflateFixed(out, data, datasize, settings); + else if(settings->btype == 2) error = deflateDynamic(out, data, datasize, settings); + else error = 61; + return error; +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Adler32 */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) +{ + unsigned s1 = adler & 0xffff; + unsigned s2 = (adler >> 16) & 0xffff; + + while(len > 0) + { + /*at least 5550 sums can be done before the sums overflow, saving a lot of module divisions*/ + unsigned amount = len > 5550 ? 5550 : len; + len -= amount; + while(amount > 0) + { + s1 = (s1 + *data++); + s2 = (s2 + s1); + amount--; + } + s1 %= 65521; + s2 %= 65521; + } + + return (s2 << 16) | s1; +} + +/*Return the adler32 of the bytes data[0..len-1]*/ +static unsigned adler32(const unsigned char* data, unsigned len) +{ + return update_adler32(1L, data, len); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Zlib / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DECODER + +unsigned LodePNG_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNG_DecompressSettings* settings) +{ + unsigned error = 0; + unsigned CM, CINFO, FDICT; + ucvector outv; + + if(insize < 2) return 53; /*error, size of zlib data too small*/ + /*read information from zlib header*/ + if((in[0] * 256 + in[1]) % 31 != 0) + { + /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/ + return 24; + } + + CM = in[0] & 15; + CINFO = (in[0] >> 4) & 15; + /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/ + FDICT = (in[1] >> 5) & 1; + /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/ + + if(CM != 8 || CINFO > 7) + { + /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/ + return 25; + } + if(FDICT != 0) + { + /*error: the specification of PNG says about the zlib stream: + "The additional flags shall not specify a preset dictionary."*/ + return 26; + } + + /*ucvector-controlled version of the output buffer, for dynamic array*/ + ucvector_init_buffer(&outv, *out, *outsize); + error = LodePNG_inflate(&outv, in, insize, 2); + *out = outv.data; + *outsize = outv.size; + if(error) return error; + + if(!settings->ignoreAdler32) + { + unsigned ADLER32 = LodePNG_read32bitInt(&in[insize - 4]); + unsigned checksum = adler32(outv.data, (unsigned)outv.size); + if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/ + } + + return 0; /*no error*/ +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +unsigned LodePNG_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNG_CompressSettings* settings) +{ + /*initially, *out must be NULL and outsize 0, if you just give some random *out + that's pointing to a non allocated buffer, this'll crash*/ + ucvector deflatedata, outv; + size_t i; + unsigned error; + + unsigned ADLER32; + /*zlib data: 1 byte CMF (CM+CINFO), 1 byte FLG, deflate data, 4 byte ADLER32 checksum of the Decompressed data*/ + unsigned CMF = 120; /*0b01111000: CM 8, CINFO 7. With CINFO 7, any window size up to 32768 can be used.*/ + unsigned FLEVEL = 0; + unsigned FDICT = 0; + unsigned CMFFLG = 256 * CMF + FDICT * 32 + FLEVEL * 64; + unsigned FCHECK = 31 - CMFFLG % 31; + CMFFLG += FCHECK; + + /*ucvector-controlled version of the output buffer, for dynamic array*/ + ucvector_init_buffer(&outv, *out, *outsize); + + ucvector_push_back(&outv, (unsigned char)(CMFFLG / 256)); + ucvector_push_back(&outv, (unsigned char)(CMFFLG % 256)); + + ucvector_init(&deflatedata); + error = LodePNG_deflate(&deflatedata, in, insize, settings); + + if(!error) + { + ADLER32 = adler32(in, (unsigned)insize); + for(i = 0; i < deflatedata.size; i++) ucvector_push_back(&outv, deflatedata.data[i]); + ucvector_cleanup(&deflatedata); + LodePNG_add32bitInt(&outv, ADLER32); + } + + *out = outv.data; + *outsize = outv.size; + + return error; +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#else /*no LODEPNG_COMPILE_ZLIB*/ + +/* +Dummy functions used if LODEPNG_COMPILE_ZLIB isn't defined. You need to implement +these yourself when disabling the LodePNG Zlib part, e.g. by calling another +library from here. + +*out must be NULL and *outsize must be 0 initially, and after the function is done, +*out must point to the decompressed data, *outsize must be the size of it, and must +be the size of the useful data in bytes, not the alloc size. +*/ + +#ifdef LODEPNG_COMPILE_DECODER +static unsigned LodePNG_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNG_DecompressSettings* settings) +{ + return 0; //Placeholder to be implemented if LODEPNG_COMPILE_ZLIB is disabled. +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER +static unsigned LodePNG_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNG_CompressSettings* settings) +{ + return 0; //Placeholder to be implemented if LODEPNG_COMPILE_ZLIB is disabled. +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/*this is a good tradeoff between speed and compression ratio*/ +#define DEFAULT_WINDOWSIZE 2048 + +void LodePNG_CompressSettings_init(LodePNG_CompressSettings* settings) +{ + /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/ + settings->btype = 2; + settings->useLZ77 = 1; + settings->windowSize = DEFAULT_WINDOWSIZE; +} + +const LodePNG_CompressSettings LodePNG_defaultCompressSettings = {2, 1, DEFAULT_WINDOWSIZE}; + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +void LodePNG_DecompressSettings_init(LodePNG_DecompressSettings* settings) +{ + settings->ignoreAdler32 = 0; +} + +const LodePNG_DecompressSettings LodePNG_defaultDecompressSettings = {0}; + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of Zlib related code. Begin of PNG related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / CRC32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned Crc32_crc_table_computed = 0; +static unsigned Crc32_crc_table[256]; + +/*Make the table for a fast CRC.*/ +static void Crc32_make_crc_table(void) +{ + unsigned c, k, n; + for(n = 0; n < 256; n++) + { + c = n; + for(k = 0; k < 8; k++) + { + if(c & 1) c = 0xedb88320L ^ (c >> 1); + else c = c >> 1; + } + Crc32_crc_table[n] = c; + } + Crc32_crc_table_computed = 1; +} + +/*Update a running CRC with the bytes buf[0..len-1]--the CRC should be +initialized to all 1's, and the transmitted value is the 1's complement of the +final running CRC (see the crc() routine below).*/ +static unsigned Crc32_update_crc(const unsigned char* buf, unsigned crc, size_t len) +{ + unsigned c = crc; + size_t n; + + if(!Crc32_crc_table_computed) Crc32_make_crc_table(); + for(n = 0; n < len; n++) + { + c = Crc32_crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8); + } + return c; +} + +/*Return the CRC of the bytes buf[0..len-1].*/ +static unsigned Crc32_crc(const unsigned char* buf, size_t len) +{ + return Crc32_update_crc(buf, 0xffffffffL, len) ^ 0xffffffffL; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing single bits and bytes from/to stream for LodePNG / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) +{ + unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1); + (*bitpointer)++; + return result; +} + +static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) +{ + unsigned result = 0; + size_t i; + for(i = nbits - 1; i < nbits; i--) + { + result += (unsigned)readBitFromReversedStream(bitpointer, bitstream) << i; + } + return result; +} + +#ifdef LODEPNG_COMPILE_DECODER +static void setBitOfReversedStream0(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) +{ + /*the current bit in bitstream must be 0 for this to work*/ + if(bit) + { + /*earlier bit of huffman code is in a lesser significant bit of an earlier byte*/ + bitstream[(*bitpointer) >> 3] |= (bit << (7 - ((*bitpointer) & 0x7))); + } + (*bitpointer)++; +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) +{ + /*the current bit in bitstream may be 0 or 1 for this to work*/ + if(bit == 0) bitstream[(*bitpointer) >> 3] &= (unsigned char)(~(1 << (7 - ((*bitpointer) & 0x7)))); + else bitstream[(*bitpointer) >> 3] |= (1 << (7 - ((*bitpointer) & 0x7))); + (*bitpointer)++; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG chunks / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +unsigned LodePNG_chunk_length(const unsigned char* chunk) +{ + return LodePNG_read32bitInt(&chunk[0]); +} + +void LodePNG_chunk_type(char type[5], const unsigned char* chunk) +{ + unsigned i; + for(i = 0; i < 4; i++) type[i] = chunk[4 + i]; + type[4] = 0; /*null termination char*/ +} + +unsigned char LodePNG_chunk_type_equals(const unsigned char* chunk, const char* type) +{ + if(strlen(type) != 4) return 0; + return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]); +} + +unsigned char LodePNG_chunk_critical(const unsigned char* chunk) +{ + return((chunk[4] & 32) == 0); +} + +unsigned char LodePNG_chunk_private(const unsigned char* chunk) +{ + return((chunk[6] & 32) != 0); +} + +unsigned char LodePNG_chunk_safetocopy(const unsigned char* chunk) +{ + return((chunk[7] & 32) != 0); +} + +unsigned char* LodePNG_chunk_data(unsigned char* chunk) +{ + return &chunk[8]; +} + +const unsigned char* LodePNG_chunk_data_const(const unsigned char* chunk) +{ + return &chunk[8]; +} + +unsigned LodePNG_chunk_check_crc(const unsigned char* chunk) +{ + unsigned length = LodePNG_chunk_length(chunk); + unsigned CRC = LodePNG_read32bitInt(&chunk[length + 8]); + /*the CRC is taken of the data and the 4 chunk type letters, not the length*/ + unsigned checksum = Crc32_crc(&chunk[4], length + 4); + if(CRC != checksum) return 1; + else return 0; +} + +void LodePNG_chunk_generate_crc(unsigned char* chunk) +{ + unsigned length = LodePNG_chunk_length(chunk); + unsigned CRC = Crc32_crc(&chunk[4], length + 4); + LodePNG_set32bitInt(chunk + 8 + length, CRC); +} + +unsigned char* LodePNG_chunk_next(unsigned char* chunk) +{ + unsigned total_chunk_length = LodePNG_chunk_length(chunk) + 12; + return &chunk[total_chunk_length]; +} + +const unsigned char* LodePNG_chunk_next_const(const unsigned char* chunk) +{ + unsigned total_chunk_length = LodePNG_chunk_length(chunk) + 12; + return &chunk[total_chunk_length]; +} + +unsigned LodePNG_append_chunk(unsigned char** out, size_t* outlength, const unsigned char* chunk) +{ + unsigned i; + unsigned total_chunk_length = LodePNG_chunk_length(chunk) + 12; + unsigned char *chunk_start, *new_buffer; + size_t new_length = (*outlength) + total_chunk_length; + if(new_length < total_chunk_length || new_length < (*outlength)) return 77; /*integer overflow happened*/ + + new_buffer = (unsigned char*)realloc(*out, new_length); + if(!new_buffer) return 9929; /*alloc fail*/ + (*out) = new_buffer; + (*outlength) = new_length; + chunk_start = &(*out)[new_length - total_chunk_length]; + + for(i = 0; i < total_chunk_length; i++) chunk_start[i] = chunk[i]; + + return 0; +} + +unsigned LodePNG_create_chunk(unsigned char** out, size_t* outlength, unsigned length, + const char* type, const unsigned char* data) +{ + unsigned i; + unsigned char *chunk, *new_buffer; + size_t new_length = (*outlength) + length + 12; + if(new_length < length + 12 || new_length < (*outlength)) return 77; /*integer overflow happened*/ + new_buffer = (unsigned char*)realloc(*out, new_length); + if(!new_buffer) return 9930; /*alloc fail*/ + (*out) = new_buffer; + (*outlength) = new_length; + chunk = &(*out)[(*outlength) - length - 12]; + + /*1: length*/ + LodePNG_set32bitInt(chunk, (unsigned)length); + + /*2: chunk name (4 letters)*/ + chunk[4] = type[0]; + chunk[5] = type[1]; + chunk[6] = type[2]; + chunk[7] = type[3]; + + /*3: the data*/ + for(i = 0; i < length; i++) chunk[8 + i] = data[i]; + + /*4: CRC (of the chunkname characters and the data)*/ + LodePNG_chunk_generate_crc(chunk); + + return 0; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Color types and such / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*return type is a LodePNG error code*/ +static unsigned checkColorValidity(unsigned colorType, unsigned bd) /*bd = bitDepth*/ +{ + switch(colorType) + { + case 0: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; /*grey*/ + case 2: if(!( bd == 8 || bd == 16)) return 37; break; /*RGB*/ + case 3: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break; /*palette*/ + case 4: if(!( bd == 8 || bd == 16)) return 37; break; /*grey + alpha*/ + case 6: if(!( bd == 8 || bd == 16)) return 37; break; /*RGBA*/ + default: return 31; + } + return 0; /*allowed color type / bits combination*/ +} + +static unsigned getNumColorChannels(unsigned colorType) +{ + switch(colorType) + { + case 0: return 1; /*grey*/ + case 2: return 3; /*RGB*/ + case 3: return 1; /*palette*/ + case 4: return 2; /*grey + alpha*/ + case 6: return 4; /*RGBA*/ + } + return 0; /*unexisting color type*/ +} + +static unsigned getBpp(unsigned colorType, unsigned bitDepth) +{ + /*bits per pixel is amount of channels * bits per channel*/ + return getNumColorChannels(colorType) * bitDepth; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +void LodePNG_InfoColor_init(LodePNG_InfoColor* info) +{ + info->key_defined = 0; + info->key_r = info->key_g = info->key_b = 0; + info->colorType = 6; + info->bitDepth = 8; + info->palette = 0; + info->palettesize = 0; +} + +void LodePNG_InfoColor_cleanup(LodePNG_InfoColor* info) +{ + LodePNG_InfoColor_clearPalette(info); +} + +void LodePNG_InfoColor_clearPalette(LodePNG_InfoColor* info) +{ + if(info->palette) free(info->palette); + info->palettesize = 0; +} + +unsigned LodePNG_InfoColor_addPalette(LodePNG_InfoColor* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + unsigned char* data; + /*the same resize technique as C++ std::vectors is used, and here it's made so that for a palette with + the max of 256 colors, it'll have the exact alloc size*/ + if(!(info->palettesize & (info->palettesize - 1))) /*if palettesize is 0 or a power of two*/ + { + /*allocated data must be at least 4* palettesize (for 4 color bytes)*/ + size_t alloc_size = info->palettesize == 0 ? 4 : info->palettesize * 4 * 2; + data = (unsigned char*)realloc(info->palette, alloc_size); + if(!data) return 9931; /*alloc fail*/ + else info->palette = data; + } + info->palette[4 * info->palettesize + 0] = r; + info->palette[4 * info->palettesize + 1] = g; + info->palette[4 * info->palettesize + 2] = b; + info->palette[4 * info->palettesize + 3] = a; + info->palettesize++; + return 0; +} + +unsigned LodePNG_InfoColor_getBpp(const LodePNG_InfoColor* info) +{ + /*calculate bits per pixel out of colorType and bitDepth*/ + return getBpp(info->colorType, info->bitDepth); +} + +unsigned LodePNG_InfoColor_getChannels(const LodePNG_InfoColor* info) +{ + return getNumColorChannels(info->colorType); +} + +unsigned LodePNG_InfoColor_isGreyscaleType(const LodePNG_InfoColor* info) +{ + return info->colorType == 0 || info->colorType == 4; +} + +unsigned LodePNG_InfoColor_isAlphaType(const LodePNG_InfoColor* info) +{ + return (info->colorType & 4) != 0; +} + +unsigned LodePNG_InfoColor_isPaletteType(const LodePNG_InfoColor* info) +{ + return info->colorType == 3; +} + +unsigned LodePNG_InfoColor_hasPaletteAlpha(const LodePNG_InfoColor* info) +{ + size_t i; + for(i = 0; i < info->palettesize; i++) + { + if(info->palette[i * 4 + 3] < 255) return 1; + } + return 0; +} + +unsigned LodePNG_InfoColor_canHaveAlpha(const LodePNG_InfoColor* info) +{ + return info->key_defined + || LodePNG_InfoColor_isAlphaType(info) + || LodePNG_InfoColor_hasPaletteAlpha(info); +} + +unsigned LodePNG_InfoColor_equal(const LodePNG_InfoColor* info1, const LodePNG_InfoColor* info2) +{ + return info1->colorType == info2->colorType + && info1->bitDepth == info2->bitDepth; /*palette and color key not compared*/ +} + +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS +void LodePNG_UnknownChunks_init(LodePNG_UnknownChunks* chunks) +{ + unsigned i; + for(i = 0; i < 3; i++) chunks->data[i] = 0; + for(i = 0; i < 3; i++) chunks->datasize[i] = 0; +} + +void LodePNG_UnknownChunks_cleanup(LodePNG_UnknownChunks* chunks) +{ + unsigned i; + for(i = 0; i < 3; i++) free(chunks->data[i]); +} + +unsigned LodePNG_UnknownChunks_copy(LodePNG_UnknownChunks* dest, const LodePNG_UnknownChunks* src) +{ + unsigned i; + + LodePNG_UnknownChunks_cleanup(dest); + + for(i = 0; i < 3; i++) + { + size_t j; + dest->datasize[i] = src->datasize[i]; + dest->data[i] = (unsigned char*)malloc(src->datasize[i]); + if(!dest->data[i] && dest->datasize[i]) return 9932; /*alloc fail*/ + for(j = 0; j < src->datasize[i]; j++) dest->data[i][j] = src->data[i][j]; + } + + return 0; +} +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +void LodePNG_Text_init(LodePNG_Text* text) +{ + text->num = 0; + text->keys = NULL; + text->strings = NULL; +} + +void LodePNG_Text_cleanup(LodePNG_Text* text) +{ + LodePNG_Text_clear(text); +} + +unsigned LodePNG_Text_copy(LodePNG_Text* dest, const LodePNG_Text* source) +{ + size_t i = 0; + dest->keys = 0; + dest->strings = 0; + dest->num = 0; + for(i = 0; i < source->num; i++) + { + unsigned error = LodePNG_Text_add(dest, source->keys[i], source->strings[i]); + if(error) return error; + } + return 0; +} + +void LodePNG_Text_clear(LodePNG_Text* text) +{ + size_t i; + for(i = 0; i < text->num; i++) + { + string_cleanup(&text->keys[i]); + string_cleanup(&text->strings[i]); + } + free(text->keys); + free(text->strings); +} + +unsigned LodePNG_Text_add(LodePNG_Text* text, const char* key, const char* str) +{ + char** new_keys = (char**)(realloc(text->keys, sizeof(char*) * (text->num + 1))); + char** new_strings = (char**)(realloc(text->strings, sizeof(char*) * (text->num + 1))); + if(!new_keys || !new_strings) + { + free(new_keys); + free(new_strings); + return 9933; /*alloc fail*/ + } + + text->num++; + text->keys = new_keys; + text->strings = new_strings; + + string_init(&text->keys[text->num - 1]); + string_set(&text->keys[text->num - 1], key); + + string_init(&text->strings[text->num - 1]); + string_set(&text->strings[text->num - 1], str); + + return 0; +} + +/******************************************************************************/ + +void LodePNG_IText_init(LodePNG_IText* text) +{ + text->num = 0; + text->keys = NULL; + text->langtags = NULL; + text->transkeys = NULL; + text->strings = NULL; +} + +void LodePNG_IText_cleanup(LodePNG_IText* text) +{ + LodePNG_IText_clear(text); +} + +unsigned LodePNG_IText_copy(LodePNG_IText* dest, const LodePNG_IText* source) +{ + size_t i = 0; + dest->keys = 0; + dest->langtags = 0; + dest->transkeys = 0; + dest->strings = 0; + dest->num = 0; + for(i = 0; i < source->num; i++) + { + unsigned error = LodePNG_IText_add(dest, source->keys[i], source->langtags[i], + source->transkeys[i], source->strings[i]); + if(error) return error; + } + return 0; +} + +void LodePNG_IText_clear(LodePNG_IText* text) +{ + size_t i; + for(i = 0; i < text->num; i++) + { + string_cleanup(&text->keys[i]); + string_cleanup(&text->langtags[i]); + string_cleanup(&text->transkeys[i]); + string_cleanup(&text->strings[i]); + } + free(text->keys); + free(text->langtags); + free(text->transkeys); + free(text->strings); +} + +unsigned LodePNG_IText_add(LodePNG_IText* text, const char* key, const char* langtag, + const char* transkey, const char* str) +{ + char** new_keys = (char**)(realloc(text->keys, sizeof(char*) * (text->num + 1))); + char** new_langtags = (char**)(realloc(text->langtags, sizeof(char*) * (text->num + 1))); + char** new_transkeys = (char**)(realloc(text->transkeys, sizeof(char*) * (text->num + 1))); + char** new_strings = (char**)(realloc(text->strings, sizeof(char*) * (text->num + 1))); + if(!new_keys || !new_langtags || !new_transkeys || !new_strings) + { + free(new_keys); + free(new_langtags); + free(new_transkeys); + free(new_strings); + return 9934; /*alloc fail*/ + } + + text->num++; + text->keys = new_keys; + text->langtags = new_langtags; + text->transkeys = new_transkeys; + text->strings = new_strings; + + string_init(&text->keys[text->num - 1]); + string_set(&text->keys[text->num - 1], key); + + string_init(&text->langtags[text->num - 1]); + string_set(&text->langtags[text->num - 1], langtag); + + string_init(&text->transkeys[text->num - 1]); + string_set(&text->transkeys[text->num - 1], transkey); + + string_init(&text->strings[text->num - 1]); + string_set(&text->strings[text->num - 1], str); + + return 0; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +void LodePNG_InfoPng_init(LodePNG_InfoPng* info) +{ + info->width = info->height = 0; + LodePNG_InfoColor_init(&info->color); + info->interlaceMethod = 0; + info->compressionMethod = 0; + info->filterMethod = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + info->background_defined = 0; + info->background_r = info->background_g = info->background_b = 0; + + LodePNG_Text_init(&info->text); + LodePNG_IText_init(&info->itext); + + info->time_defined = 0; + info->phys_defined = 0; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + LodePNG_UnknownChunks_init(&info->unknown_chunks); +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ +} + +void LodePNG_InfoPng_cleanup(LodePNG_InfoPng* info) +{ + LodePNG_InfoColor_cleanup(&info->color); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + LodePNG_Text_cleanup(&info->text); + LodePNG_IText_cleanup(&info->itext); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + LodePNG_UnknownChunks_cleanup(&info->unknown_chunks); +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ +} + +unsigned LodePNG_InfoPng_copy(LodePNG_InfoPng* dest, const LodePNG_InfoPng* source) +{ + unsigned error = 0; + LodePNG_InfoPng_cleanup(dest); + *dest = *source; + LodePNG_InfoColor_init(&dest->color); + error = LodePNG_InfoColor_copy(&dest->color, &source->color); if(error) return error; + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + error = LodePNG_Text_copy(&dest->text, &source->text); if(error) return error; + error = LodePNG_IText_copy(&dest->itext, &source->itext); if(error) return error; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + LodePNG_UnknownChunks_init(&dest->unknown_chunks); + error = LodePNG_UnknownChunks_copy(&dest->unknown_chunks, &source->unknown_chunks); if(error) return error; +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + return error; +} + +void LodePNG_InfoPng_swap(LodePNG_InfoPng* a, LodePNG_InfoPng* b) +{ + LodePNG_InfoPng temp = *a; + *a = *b; + *b = temp; +} + +unsigned LodePNG_InfoColor_copy(LodePNG_InfoColor* dest, const LodePNG_InfoColor* source) +{ + size_t i; + LodePNG_InfoColor_cleanup(dest); + *dest = *source; + dest->palette = (unsigned char*)malloc(source->palettesize * 4); + if(!dest->palette && source->palettesize) return 9935; /*alloc fail*/ + for(i = 0; i < source->palettesize * 4; i++) dest->palette[i] = source->palette[i]; + return 0; +} + +void LodePNG_InfoRaw_init(LodePNG_InfoRaw* info) +{ + LodePNG_InfoColor_init(&info->color); +} + +void LodePNG_InfoRaw_cleanup(LodePNG_InfoRaw* info) +{ + LodePNG_InfoColor_cleanup(&info->color); +} + +unsigned LodePNG_InfoRaw_copy(LodePNG_InfoRaw* dest, const LodePNG_InfoRaw* source) +{ + unsigned error = 0; + LodePNG_InfoRaw_cleanup(dest); + *dest = *source; + LodePNG_InfoColor_init(&dest->color); + error = LodePNG_InfoColor_copy(&dest->color, &source->color); + return error; /*this variable could be removed, but it's more clear what is returned this way*/ +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +/*convert from any color type to RGB or RGBA with 8 bits per sample*/ +static unsigned LodePNG_convert_rgb_a_8(unsigned char* out, const unsigned char* in, + LodePNG_InfoColor* infoIn, size_t numpixels, unsigned bytes, unsigned alpha) +{ + size_t i, c, bp = 0; /*bp = bitpointer, used by less-than-8-bit color types*/ + + if(infoIn->bitDepth == 8) + { + switch(infoIn->colorType) + { + case 0: /*greyscale color*/ + for(i = 0; i < numpixels; i++) + { + out[bytes * i + 0] = out[bytes * i + 1] = out[bytes * i + 2] = in[i]; + if(alpha) out[bytes * i + 3] = infoIn->key_defined && in[i] == infoIn->key_r ? 0 : 255; + } + break; + case 2: /*RGB color*/ + for(i = 0; i < numpixels; i++) + { + for(c = 0; c < 3; c++) out[bytes * i + c] = in[3 * i + c]; + if(alpha) + { + if(infoIn->key_defined == 1 && in[3 * i + 0] == infoIn->key_r + && in[3 * i + 1] == infoIn->key_g && in[3 * i + 2] == infoIn->key_b) + out[bytes * i + 3] = 0; + else out[bytes * i + 3] = 255; + } + } + break; + case 3: /*indexed color (palette)*/ + for(i = 0; i < numpixels; i++) + { + if(in[i] >= infoIn->palettesize) return 46; /*invalid palette index*/ + for(c = 0; c < bytes; c++) out[bytes * i + c] = infoIn->palette[4 * in[i] + c]; + } + break; + case 4: /*greyscale with alpha*/ + for(i = 0; i < numpixels; i++) + { + out[bytes * i + 0] = out[bytes * i + 1] = out[bytes * i + 2] = in[2 * i + 0]; + if(alpha) out[bytes * i + 3] = in[2 * i + 1]; + } + break; + case 6: /*RGB with alpha*/ + for(i = 0; i < numpixels; i++) + { + for(c = 0; c < bytes; c++) out[bytes * i + c] = in[4 * i + c]; + } + break; + default: break; + } + } + else if(infoIn->bitDepth == 16) + { + switch(infoIn->colorType) + { + case 0: /*greyscale color*/ + for(i = 0; i < numpixels; i++) + { + out[bytes * i + 0] = out[bytes * i + 1] = out[bytes * i + 2] = in[2 * i]; + if(alpha) out[bytes * i + 3] = infoIn->key_defined && 256U * in[i] + in[i + 1] == infoIn->key_r ? 0 : 255; + } + break; + case 2: /*RGB color*/ + for(i = 0; i < numpixels; i++) + { + if(alpha) out[bytes * i + 3] = 255; + for(c = 0; c < 3; c++) out[bytes * i + c] = in[6 * i + 2 * c]; + if(alpha) + { + if(infoIn->key_defined && 256U * in[6 * i + 0] + in[6 * i + 1] == infoIn->key_r + && 256U * in[6 * i + 2] + in[6 * i + 3] == infoIn->key_g + && 256U * in[6 * i + 4] + in[6 * i + 5] == infoIn->key_b) + out[bytes * i + 3] = 0; + else out[bytes * i + 3] = 255; + } + } + break; + case 4: /*greyscale with alpha*/ + for(i = 0; i < numpixels; i++) + { + out[bytes * i + 0] = out[bytes * i + 1] = out[bytes * i + 2] = in[4 * i]; + if(alpha) out[bytes * i + 3] = in[4 * i + 2]; + } + break; + case 6: /*RGB with alpha*/ + for(i = 0; i < numpixels; i++) + { + for(c = 0; c < bytes; c++) out[bytes * i + c] = in[8 * i + 2 * c]; + } + break; + default: break; + } + } + else /*infoIn->bitDepth is less than 8 bit per channel*/ + { + switch(infoIn->colorType) + { + case 0: /*greyscale color*/ + for(i = 0; i < numpixels; i++) + { + unsigned value = readBitsFromReversedStream(&bp, in, infoIn->bitDepth); + unsigned highest = ((1U << infoIn->bitDepth) - 1U); /*highest possible value for this bit depth*/ + /*scale value from 0 to 255*/ + out[bytes * i + 0] = out[bytes * i + 1] = out[bytes * i + 2] + = (unsigned char)((value * 255) / highest); + if(alpha) out[bytes * i + 3] = infoIn->key_defined && value == infoIn->key_r ? 0 : 255; + } + break; + case 3: /*indexed color (palette)*/ + for(i = 0; i < numpixels; i++) + { + unsigned value = readBitsFromReversedStream(&bp, in, infoIn->bitDepth); + if(value >= infoIn->palettesize) return 47; + for(c = 0; c < bytes; c++) out[bytes * i + c] = infoIn->palette[4 * value + c]; + } + break; + default: break; + } + } + return 0; +} + +/*convert from any greyscale color type to 8-bit greyscale with or without alpha channel*/ +static unsigned LodePNG_convert_grey_8(unsigned char* out, const unsigned char* in, + LodePNG_InfoColor* infoIn, size_t numpixels, unsigned bytes, unsigned alpha) +{ + size_t i, bp = 0; /*bp = bitpointer, used by less-than-8-bit color types*/ + + if(infoIn->bitDepth == 8) + { + switch(infoIn->colorType) + { + case 0: /*greyscale color*/ + for(i = 0; i < numpixels; i++) + { + out[bytes * i] = in[i]; + if(alpha) out[bytes * i + 1] = infoIn->key_defined && in[i] == infoIn->key_r ? 0 : 255; + } + break; + case 4: /*greyscale with alpha*/ + for(i = 0; i < numpixels; i++) + { + out[bytes * i + 0] = in[2 * i + 0]; + if(alpha) out[bytes * i + 1] = in[2 * i + 1]; + } + break; + default: return 31; + } + } + else if(infoIn->bitDepth == 16) + { + switch(infoIn->colorType) + { + case 0: /*greyscale color*/ + for(i = 0; i < numpixels; i++) + { + if(alpha) out[bytes * i + 1] = 255; + out[bytes * i] = in[2 * i]; + if(alpha && infoIn->key_defined && 256U * in[i] + in[i + 1] == infoIn->key_r) + { + out[bytes * i + 1] = 0; + } + } + break; + case 4: /*greyscale with alpha*/ + for(i = 0; i < numpixels; i++) + { + out[bytes * i] = in[4 * i]; /*most significant byte*/ + if(alpha) out[bytes * i + 1] = in[4 * i + 2]; /*most significant byte*/ + } + break; + default: return 31; + } + } + else /*infoIn->bitDepth is less than 8 bit per channel*/ + { + if(infoIn->colorType != 0) return 31; /*colorType 0 is the only greyscale type with < 8 bits per channel*/ + for(i = 0; i < numpixels; i++) + { + unsigned value = readBitsFromReversedStream(&bp, in, infoIn->bitDepth); + unsigned highest = ((1U << infoIn->bitDepth) - 1U); /*highest possible value for this bit depth*/ + out[bytes * i] = (unsigned char)((value * 255) / highest); /*scale value from 0 to 255*/ + if(alpha) + { + if(infoIn->key_defined && value == infoIn->key_r) out[bytes * i + 1] = 0; + else out[bytes * i + 1] = 255; + } + } + } + return 0; +} + +/*convert from any color type to RGB or RGBA with 8 bits per sample*/ +static unsigned LodePNG_convert_rgb_a_16(unsigned char* out, const unsigned char* in, + LodePNG_InfoColor* infoIn, size_t numpixels, unsigned bytes, unsigned alpha) +{ + size_t i, c, bp = 0; /*bp = bitpointer, used by less-than-8-bit color types*/ + + if(infoIn->bitDepth == 8) + { + switch(infoIn->colorType) + { + case 0: /*greyscale color*/ + for(i = 0; i < numpixels; i++) + { + out[bytes * i + 0] = out[bytes * i + 2] = out[bytes * i + 4] = in[i]; + out[bytes * i + 1] = out[bytes * i + 3] = out[bytes * i + 5] = in[i]; + if(alpha) + { + if(alpha && infoIn->key_defined && in[i] == infoIn->key_r) out[bytes * i + 6] = out[bytes * i + 7] = 0; + else out[bytes * i + 6] = out[bytes * i + 7] = 255; + } + } + break; + case 2: /*RGB color*/ + for(i = 0; i < numpixels; i++) + { + out[bytes * i + 0] = out[bytes * i + 1] = in[3 * i + 0]; + out[bytes * i + 2] = out[bytes * i + 3] = in[3 * i + 1]; + out[bytes * i + 4] = out[bytes * i + 5] = in[3 * i + 2]; + if(alpha) + { + if(infoIn->key_defined == 1 && in[3 * i + 0] == infoIn->key_r + && in[3 * i + 1] == infoIn->key_g && in[3 * i + 2] == infoIn->key_b) + out[bytes * i + 6] = out[bytes * i + 7] = 0; + else out[bytes * i + 6] = out[bytes * i + 7] = 255; + } + } + break; + case 3: /*indexed color (palette)*/ + for(i = 0; i < numpixels; i++) + { + if(in[i] >= infoIn->palettesize) return 46; /*invalid palette index*/ + for(c = 0; c < bytes; c++) + { + out[bytes * i + 2 * c] = out[bytes * i + 2 * c + 1] = infoIn->palette[4 * in[i] + c]; + } + } + break; + case 4: /*greyscale with alpha*/ + for(i = 0; i < numpixels; i++) + { + out[bytes * i + 0] = out[bytes * i + 2] = out[bytes * i + 4] = in[i]; + out[bytes * i + 1] = out[bytes * i + 3] = out[bytes * i + 5] = in[i]; + if(alpha) + { + out[bytes * i + 6] = out[bytes * i + 7] = in[2 * i + 1]; + } + } + break; + case 6: /*RGB with alpha*/ + for(i = 0; i < numpixels; i++) + { + for(c = 0; c < 3; c++) + { + out[bytes * i + 2 * c] = out[bytes * i + 2 * c + 1] = in[4 * i + c]; + } + if(alpha) + { + out[bytes * i + 6] = out[bytes * i + 7] = in[4 * i + c]; + } + } + break; + default: break; + } + } + else if(infoIn->bitDepth == 16) + { + switch(infoIn->colorType) + { + case 0: /*greyscale color*/ + for(i = 0; i < numpixels; i++) + { + out[bytes * i + 0] = out[bytes * i + 2] = out[bytes * i + 4] = in[2 * i]; + out[bytes * i + 1] = out[bytes * i + 3] = out[bytes * i + 5] = in[2 * i + 1]; + if(alpha) + { + if(infoIn->key_defined && 256U * in[i] + in[i + 1] == infoIn->key_r) + out[bytes * i + 6] = out[bytes * i + 7] = 0; + else out[bytes * i + 6] = out[bytes * i + 7] = 255; + } + } + break; + case 2: /*RGB color*/ + for(i = 0; i < numpixels; i++) + { + for(c = 0; c < 6; c++) out[bytes * i + c] = in[6 * i + c]; + if(alpha) + { + if(infoIn->key_defined && 256U * in[6 * i + 0] + in[6 * i + 1] == infoIn->key_r + && 256U * in[6 * i + 2] + in[6 * i + 3] == infoIn->key_g + && 256U * in[6 * i + 4] + in[6 * i + 5] == infoIn->key_b) + out[bytes * i + 6] = out[bytes * i + 7] = 0; + else out[bytes * i + 6] = out[bytes * i + 7] = 255; + } + } + break; + case 4: /*greyscale with alpha*/ + for(i = 0; i < numpixels; i++) + { + out[bytes * i + 0] = out[bytes * i + 2] = out[bytes * i + 4] = in[4 * i + 0]; + out[bytes * i + 1] = out[bytes * i + 3] = out[bytes * i + 5] = in[4 * i + 1]; + if(alpha) + { + out[bytes * i + 6] = in[4 * i + 2]; + out[bytes * i + 7] = in[4 * i + 3]; + } + } + break; + case 6: /*RGB with alpha*/ + for(i = 0; i < numpixels; i++) + { + for(c = 0; c < bytes; c++) out[bytes * i + c] = in[8 * i + 2 * c]; + } + break; + default: break; + } + } + else /*infoIn->bitDepth is less than 8 bit per channel*/ + { + switch(infoIn->colorType) + { + case 0: /*greyscale color*/ + for(i = 0; i < numpixels; i++) + { + unsigned value = readBitsFromReversedStream(&bp, in, infoIn->bitDepth); + unsigned highest = ((1U << infoIn->bitDepth) - 1U); /*highest possible value for this bit depth*/ + /*scale value from 0 to 255*/ + out[bytes * i + 0] = out[bytes * i + 2] = out[bytes * i + 4] + = out[bytes * i + 1] = out[bytes * i + 3] + = out[bytes * i + 5] = (unsigned char)((value * 255) / highest); + if(alpha) + { + if(infoIn->key_defined && value == infoIn->key_r) out[bytes * i + 6] = out[bytes * i + 7] = 0; + else out[bytes * i + 6] = out[bytes * i + 7] = 255; + } + } + break; + case 3: /*indexed color (palette)*/ + for(i = 0; i < numpixels; i++) + { + unsigned value = readBitsFromReversedStream(&bp, in, infoIn->bitDepth); + if(value >= infoIn->palettesize) return 47; + for(c = 0; c < bytes / 2; c++) + { + out[bytes * i + c * 2 + 0] = out[bytes * i + c * 2 + 1] = infoIn->palette[4 * value + c]; + } + } + break; + default: break; + } + } + return 0; +} + +/*convert from any greyscale color type to 16-bit greyscale with or without alpha channel*/ +static unsigned LodePNG_convert_grey_16(unsigned char* out, const unsigned char* in, + LodePNG_InfoColor* infoIn, size_t numpixels, unsigned bytes, unsigned alpha) +{ + size_t i, c, bp = 0; /*bp = bitpointer, used by less-than-8-bit color types*/ + + if(infoIn->bitDepth == 8) + { + switch(infoIn->colorType) + { + case 0: /*greyscale color*/ + for(i = 0; i < numpixels; i++) + { + out[bytes * i] = out[bytes * i + 1] = in[i]; + if(alpha) + { + if(infoIn->key_defined && in[i] == infoIn->key_r) out[bytes * i + 2] = out[bytes * i + 3] = 0; + else out[bytes * i + 2] = out[bytes * i + 3] = 255; + } + } + break; + case 4: /*greyscale with alpha*/ + for(i = 0; i < numpixels; i++) + { + out[bytes * i + 0] = out[bytes * i + 1] = in[2 * i + 0]; + if(alpha) out[bytes * i + 2] = out[bytes * i + 3] = in[2 * i + 1]; + } + break; + default: return 31; + } + } + else if(infoIn->bitDepth == 16) + { + switch(infoIn->colorType) + { + case 0: /*greyscale color*/ + for(i = 0; i < numpixels; i++) + { + out[bytes * i + 0] = in[2 * i + 0]; + out[bytes * i + 1] = in[2 * i + 1]; + if(alpha) + { + if(infoIn->key_defined && 256U * in[i] + in[i + 1] == infoIn->key_r) + out[bytes * i + 2] = out[bytes * i + 3] = 0; + else out[bytes * i + 2] = out[bytes * i + 3] = 255; + } + } + break; + case 4: /*greyscale with alpha*/ + for(i = 0; i < numpixels; i++) + { + for(c = 0; c < bytes; c++) out[bytes * i + c] = in[4 * i + c]; + } + break; + default: return 31; + } + } + else /*infoIn->bitDepth is less than 8 bit per channel*/ + { + if(infoIn->colorType != 0) return 31; /*colorType 0 is the only greyscale type with < 8 bits per channel*/ + for(i = 0; i < numpixels; i++) + { + unsigned value = readBitsFromReversedStream(&bp, in, infoIn->bitDepth); + unsigned highest = ((1U << infoIn->bitDepth) - 1U); /*highest possible value for this bit depth*/ + out[bytes * i] = out[bytes * i + 1] = (unsigned char)((value * 255) / highest); /*scale value from 0 to 255*/ + if(alpha) + { + if(infoIn->key_defined && value == infoIn->key_r) out[bytes * i + 2] = out[bytes * i + 3] = 0; + else out[bytes * i + 2] = out[bytes * i + 3] = 255; + } + } + } + return 0; +} + +/* +converts from any color type to 24-bit or 32-bit (later maybe more supported). return value = LodePNG error code +the out buffer must have (w * h * bpp + 7) / 8 bytes, where bpp is the bits per pixel of the output color type +(LodePNG_InfoColor_getBpp) for < 8 bpp images, there may _not_ be padding bits at the end of scanlines. +*/ +unsigned LodePNG_convert(unsigned char* out, const unsigned char* in, LodePNG_InfoColor* infoOut, + LodePNG_InfoColor* infoIn, unsigned w, unsigned h) +{ + size_t numpixels = w * h; /*amount of pixels*/ + unsigned bytes = LodePNG_InfoColor_getBpp(infoOut) / 8; /*bytes per pixel in the output image*/ + unsigned alpha = LodePNG_InfoColor_isAlphaType(infoOut); /*use 8-bit alpha channel*/ + + /*cases where in and out already have the same format*/ + if(LodePNG_InfoColor_equal(infoIn, infoOut)) + { + size_t i; + size_t size = (numpixels * LodePNG_InfoColor_getBpp(infoIn) + 7) / 8; + for(i = 0; i < size; i++) out[i] = in[i]; + return 0; + } + else if((infoOut->colorType == 2 || infoOut->colorType == 6) && infoOut->bitDepth == 8) + { + LodePNG_convert_rgb_a_8(out, in, infoIn, numpixels, bytes, alpha); + } + else if(LodePNG_InfoColor_isGreyscaleType(infoOut) && infoOut->bitDepth == 8) + { + /*conversion from greyscale to greyscale*/ + if(!LodePNG_InfoColor_isGreyscaleType(infoIn)) return 62; /*converting from color to grey is not supported*/ + LodePNG_convert_grey_8(out, in, infoIn, numpixels, bytes, alpha); + } + else if((infoOut->colorType == 2 || infoOut->colorType == 6) && infoOut->bitDepth == 16) + { + LodePNG_convert_rgb_a_16(out, in, infoIn, numpixels, bytes, alpha); + } + else if(LodePNG_InfoColor_isGreyscaleType(infoOut) && infoOut->bitDepth == 16) + { + /*conversion from greyscale to greyscale*/ + if(!LodePNG_InfoColor_isGreyscaleType(infoIn)) return 62; /*converting from color to grey is not supported*/ + LodePNG_convert_grey_16(out, in, infoIn, numpixels, bytes, alpha); + } + else return 59; /*invalid color mode*/ + return 0; +} + +/* +Paeth predicter, used by PNG filter type 4 +The parameters are of type short, but should come from unsigned chars, the shorts +are only needed to make the paeth calculation correct. +*/ +static unsigned char paethPredictor(short a, short b, short c) +{ + short pa = abs(b - c); + short pb = abs(a - c); + short pc = abs(a + b - c - c); + + /*short pc = a + b - c; + short pa = abs(pc - a); + short pb = abs(pc - b); + pc = abs(pc - c);*/ + + if(pa <= pb && pa <= pc) return (unsigned char)a; + else if(pb <= pc) return (unsigned char)b; + else return (unsigned char)c; +} + +/*shared values used by multiple Adam7 related functions*/ + +static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/ +static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/ +static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/ +static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/ + +static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], + size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) +{ + /*the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass*/ + unsigned i; + + /*calculate width and height in pixels of each pass*/ + for(i = 0; i < 7; i++) + { + passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; + passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; + if(passw[i] == 0) passh[i] = 0; + if(passh[i] == 0) passw[i] = 0; + } + + filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; + for(i = 0; i < 7; i++) + { + /*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/ + filter_passstart[i + 1] = filter_passstart[i] + + ((passw[i] && passh[i]) ? passh[i] * (1 + (passw[i] * bpp + 7) / 8) : 0); + /*bits padded if needed to fill full byte at end of each scanline*/ + padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7) / 8); + /*only padded at end of reduced image*/ + passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7) / 8; + } +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Decoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*read the information from the header and store it in the LodePNG_Info. return value is error*/ +void LodePNG_Decoder_inspect(LodePNG_Decoder* decoder, const unsigned char* in, size_t inlength) +{ + if(inlength == 0 || in == 0) + { + decoder->error = 48; /*the given data is empty*/ + return; + } + if(inlength < 29) + { + decoder->error = 27; /*error: the data length is smaller than the length of a PNG header*/ + return; + } + + /*when decoding a new PNG image, make sure all parameters created after previous decoding are reset*/ + LodePNG_InfoPng_cleanup(&decoder->infoPng); + LodePNG_InfoPng_init(&decoder->infoPng); + decoder->error = 0; + + if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 + || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) + { + decoder->error = 28; /*error: the first 8 bytes are not the correct PNG signature*/ + return; + } + if(in[12] != 'I' || in[13] != 'H' || in[14] != 'D' || in[15] != 'R') + { + decoder->error = 29; /*error: it doesn't start with a IHDR chunk!*/ + return; + } + + /*read the values given in the header*/ + decoder->infoPng.width = LodePNG_read32bitInt(&in[16]); + decoder->infoPng.height = LodePNG_read32bitInt(&in[20]); + decoder->infoPng.color.bitDepth = in[24]; + decoder->infoPng.color.colorType = in[25]; + decoder->infoPng.compressionMethod = in[26]; + decoder->infoPng.filterMethod = in[27]; + decoder->infoPng.interlaceMethod = in[28]; + + if(!decoder->settings.ignoreCrc) + { + unsigned CRC = LodePNG_read32bitInt(&in[29]); + unsigned checksum = Crc32_crc(&in[12], 17); + if(CRC != checksum) + { + decoder->error = 57; /*invalid CRC*/ + return; + } + } + + /*error: only compression method 0 is allowed in the specification*/ + if(decoder->infoPng.compressionMethod != 0) { decoder->error = 32; return; } + /*error: only filter method 0 is allowed in the specification*/ + if(decoder->infoPng.filterMethod != 0) { decoder->error = 33; return; } + /*error: only interlace methods 0 and 1 exist in the specification*/ + if(decoder->infoPng.interlaceMethod > 1) { decoder->error = 34; return; } + + decoder->error = checkColorValidity(decoder->infoPng.color.colorType, decoder->infoPng.color.bitDepth); +} + +static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, + size_t bytewidth, unsigned char filterType, size_t length) +{ + /* + For PNG filter method 0 + unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, + the filter works byte per byte (bytewidth = 1) + precon is the previous unfiltered scanline, recon the result, scanline the current one + the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead + recon and scanline MAY be the same memory address! precon must be disjoint. + */ + + size_t i; + switch(filterType) + { + case 0: + for(i = 0; i < length; i++) recon[i] = scanline[i]; + break; + case 1: + for(i = 0; i < bytewidth; i++) recon[i] = scanline[i]; + for(i = bytewidth; i < length; i++) recon[i] = scanline[i] + recon[i - bytewidth]; + break; + case 2: + if(precon) + { + for(i = 0; i < length; i++) recon[i] = scanline[i] + precon[i]; + } + else + { + for(i = 0; i < length; i++) recon[i] = scanline[i]; + } + break; + case 3: + if(precon) + { + for(i = 0; i < bytewidth; i++) recon[i] = scanline[i] + precon[i] / 2; + for(i = bytewidth; i < length; i++) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) / 2); + } + else + { + for(i = 0; i < bytewidth; i++) recon[i] = scanline[i]; + for(i = bytewidth; i < length; i++) recon[i] = scanline[i] + recon[i - bytewidth] / 2; + } + break; + case 4: + if(precon) + { + for(i = 0; i < bytewidth; i++) + { + recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/ + } + for(i = bytewidth; i < length; i++) + { + recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth])); + } + } + else + { + for(i = 0; i < bytewidth; i++) + { + recon[i] = scanline[i]; + } + for(i = bytewidth; i < length; i++) + { + /*paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth]*/ + recon[i] = (scanline[i] + recon[i - bytewidth]); + } + } + break; + default: return 36; /*error: unexisting filter type given*/ + } + return 0; +} + +static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + /* + For PNG filter method 0 + this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times) + out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline + w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel + in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes) + */ + + unsigned y; + unsigned char* prevline = 0; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7) / 8; + size_t linebytes = (w * bpp + 7) / 8; + + for(y = 0; y < h; y++) + { + size_t outindex = linebytes * y; + size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + unsigned char filterType = in[inindex]; + + unsigned error = unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes); + if(error) return error; + + prevline = &out[outindex]; + } + + return 0; +} + +static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + /*Note: this function works on image buffers WITHOUT padding bits at end of scanlines + with non-multiple-of-8 bit amounts, only between reduced images is padding + out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation + (because that's likely a little bit faster)*/ + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) + { + for(i = 0; i < 7; i++) + { + unsigned x, y, b; + size_t bytewidth = bpp / 8; + for(y = 0; y < passh[i]; y++) + for(x = 0; x < passw[i]; x++) + { + size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth; + size_t pixeloutstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + for(b = 0; b < bytewidth; b++) + { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } + else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ + { + for(i = 0; i < 7; i++) + { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; y++) + for(x = 0; x < passw[i]; x++) + { + ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + obp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + for(b = 0; b < bpp; b++) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + /*note that this function assumes the out buffer is completely 0, use setBitOfReversedStream otherwise*/ + setBitOfReversedStream0(&obp, out, bit); + } + } + } + } +} + +static void removePaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) +{ + /* + After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need + to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers + for the Adam7 code, the color convert code and the output to the user. + in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must + have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits + also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 + only useful if (ilinebits - olinebits) is a value in the range 1..7 + */ + unsigned y; + size_t diff = ilinebits - olinebits; + size_t ibp = 0, obp = 0; /*input and output bit pointers*/ + for(y = 0; y < h; y++) + { + size_t x; + for(x = 0; x < olinebits; x++) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + ibp += diff; + } +} + +/*out must be buffer big enough to contain full image, and in must contain the full decompressed data from +the IDAT chunks (with filter index bytes and possible padding bits) +return value is error*/ +static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, const LodePNG_InfoPng* infoPng) +{ + /* + This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype. + Steps: + *) if no Adam7: 1) unfilter 2) remove padding bits (= posible extra bits per scanline if bpp < 8) + *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace + NOTE: the in buffer will be overwritten with intermediate data! + */ + unsigned bpp = LodePNG_InfoColor_getBpp(&infoPng->color); + unsigned w = infoPng->width; + unsigned h = infoPng->height; + unsigned error = 0; + if(bpp == 0) return 31; /*error: invalid colortype*/ + + if(infoPng->interlaceMethod == 0) + { + if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8) + { + error = unfilter(in, in, w, h, bpp); + if(error) return error; + removePaddingBits(out, in, w * bpp, ((w * bpp + 7) / 8) * 8, h); + } + else error = unfilter(out, in, w, h, bpp); /*we can immediatly filter into the out buffer, no other steps needed*/ + } + else /*interlaceMethod is 1 (Adam7)*/ + { + unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + for(i = 0; i < 7; i++) + { + error = unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp); + if(error) return error; + /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline, + move bytes instead of bits or move not at all*/ + if(bpp < 8) + { + /*remove padding bits in scanlines; after this there still may be padding + bits between the different reduced images: each reduced image still starts nicely at a byte*/ + removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, + ((passw[i] * bpp + 7) / 8) * 8, passh[i]); + } + } + + Adam7_deinterlace(out, in, w, h, bpp); + } + + return error; +} + +/*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/ +static void decodeGeneric(LodePNG_Decoder* decoder, unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize) +{ + unsigned char IEND = 0; + const unsigned char* chunk; + size_t i; + ucvector idat; /*the data from idat chunks*/ + + /*for unknown chunk order*/ + unsigned unknown = 0; +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + unsigned critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + + LodePNG_Decoder_inspect(decoder, in, insize); /*reads header and resets other parameters in decoder->infoPng*/ + if(decoder->error) return; + + ucvector_init(&idat); + + chunk = &in[33]; /*first byte of the first chunk after the header*/ + + /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. + IDAT data is put at the start of the in buffer*/ + while(!IEND) + { + unsigned chunkLength; + const unsigned char* data; /*the data in the chunk*/ + + /*error: size of the in buffer too small to contain next chunk*/ + if((size_t)((chunk - in) + 12) > insize || chunk < in) CERROR_BREAK(decoder->error, 30); + + /*length of the data of the chunk, excluding the length bytes, chunk type and CRC bytes*/ + chunkLength = LodePNG_chunk_length(chunk); + /*error: chunk length larger than the max PNG chunk size*/ + if(chunkLength > 2147483647) CERROR_BREAK(decoder->error, 63); + + if((size_t)((chunk - in) + chunkLength + 12) > insize || (chunk + chunkLength + 12) < in) + { + CERROR_BREAK(decoder->error, 64); /*error: size of the in buffer too small to contain next chunk*/ + } + + data = LodePNG_chunk_data_const(chunk); + + /*IDAT chunk, containing compressed image data*/ + if(LodePNG_chunk_type_equals(chunk, "IDAT")) + { + size_t oldsize = idat.size; + if(!ucvector_resize(&idat, oldsize + chunkLength)) CERROR_BREAK(decoder->error, 9936 /*alloc fail*/); + for(i = 0; i < chunkLength; i++) idat.data[oldsize + i] = data[i]; +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + critical_pos = 3; +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + } + /*IEND chunk*/ + else if(LodePNG_chunk_type_equals(chunk, "IEND")) + { + IEND = 1; + } + /*palette chunk (PLTE)*/ + else if(LodePNG_chunk_type_equals(chunk, "PLTE")) + { + unsigned pos = 0; + if(decoder->infoPng.color.palette) free(decoder->infoPng.color.palette); + decoder->infoPng.color.palettesize = chunkLength / 3; + decoder->infoPng.color.palette = (unsigned char*)malloc(4 * decoder->infoPng.color.palettesize); + if(!decoder->infoPng.color.palette && decoder->infoPng.color.palettesize) + { + decoder->infoPng.color.palettesize = 0; + CERROR_BREAK(decoder->error, 9937); /*alloc fail*/ + } + if(decoder->infoPng.color.palettesize > 256) CERROR_BREAK(decoder->error, 38); /*error: palette too big*/ + + for(i = 0; i < decoder->infoPng.color.palettesize; i++) + { + decoder->infoPng.color.palette[4 * i + 0] = data[pos++]; /*R*/ + decoder->infoPng.color.palette[4 * i + 1] = data[pos++]; /*G*/ + decoder->infoPng.color.palette[4 * i + 2] = data[pos++]; /*B*/ + decoder->infoPng.color.palette[4 * i + 3] = 255; /*alpha*/ + } +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + critical_pos = 2; +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + } + /*palette transparency chunk (tRNS)*/ + else if(LodePNG_chunk_type_equals(chunk, "tRNS")) + { + if(decoder->infoPng.color.colorType == 3) + { + /*error: more alpha values given than there are palette entries*/ + if(chunkLength > decoder->infoPng.color.palettesize) CERROR_BREAK(decoder->error, 38); + + for(i = 0; i < chunkLength; i++) decoder->infoPng.color.palette[4 * i + 3] = data[i]; + } + else if(decoder->infoPng.color.colorType == 0) + { + /*error: this chunk must be 2 bytes for greyscale image*/ + if(chunkLength != 2) CERROR_BREAK(decoder->error, 30); + + decoder->infoPng.color.key_defined = 1; + decoder->infoPng.color.key_r = decoder->infoPng.color.key_g + = decoder->infoPng.color.key_b = 256 * data[0] + data[1]; + } + else if(decoder->infoPng.color.colorType == 2) + { + /*error: this chunk must be 6 bytes for RGB image*/ + if(chunkLength != 6) CERROR_BREAK(decoder->error, 41); + + decoder->infoPng.color.key_defined = 1; + decoder->infoPng.color.key_r = 256 * data[0] + data[1]; + decoder->infoPng.color.key_g = 256 * data[2] + data[3]; + decoder->infoPng.color.key_b = 256 * data[4] + data[5]; + } + else CERROR_BREAK(decoder->error, 42); /*error: tRNS chunk not allowed for other color models*/ + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*background color chunk (bKGD)*/ + else if(LodePNG_chunk_type_equals(chunk, "bKGD")) + { + if(decoder->infoPng.color.colorType == 3) + { + /*error: this chunk must be 1 byte for indexed color image*/ + if(chunkLength != 1) CERROR_BREAK(decoder->error, 43); + + decoder->infoPng.background_defined = 1; + decoder->infoPng.background_r = decoder->infoPng.background_g = decoder->infoPng.background_b = data[0]; + } + else if(decoder->infoPng.color.colorType == 0 || decoder->infoPng.color.colorType == 4) + { + /*error: this chunk must be 2 bytes for greyscale image*/ + if(chunkLength != 2) CERROR_BREAK(decoder->error, 44); + + decoder->infoPng.background_defined = 1; + decoder->infoPng.background_r = decoder->infoPng.background_g = decoder->infoPng.background_b + = 256 * data[0] + data[1]; + } + else if(decoder->infoPng.color.colorType == 2 || decoder->infoPng.color.colorType == 6) + { + /*error: this chunk must be 6 bytes for greyscale image*/ + if(chunkLength != 6) CERROR_BREAK(decoder->error, 45); + + decoder->infoPng.background_defined = 1; + decoder->infoPng.background_r = 256 * data[0] + data[1]; + decoder->infoPng.background_g = 256 * data[2] + data[3]; + decoder->infoPng.background_b = 256 * data[4] + data[5]; + } + } + /*text chunk (tEXt)*/ + else if(LodePNG_chunk_type_equals(chunk, "tEXt")) + { + if(decoder->settings.readTextChunks) + { + char *key = 0, *str = 0; + + while(!decoder->error) /*not really a while loop, only used to break on error*/ + { + unsigned length, string2_begin; + + length = 0; + while(length < chunkLength && data[length] != 0) length++; + /*error, end reached, no null terminator?*/ + if(length + 1 >= chunkLength) CERROR_BREAK(decoder->error, 75); + + key = (char*)malloc(length + 1); + if(!key) CERROR_BREAK(decoder->error, 9938); /*alloc fail*/ + + key[length] = 0; + for(i = 0; i < length; i++) key[i] = data[i]; + + string2_begin = length + 1; + /*error, end reached, no null terminator?*/ + if(string2_begin > chunkLength) CERROR_BREAK(decoder->error, 75); + + length = chunkLength - string2_begin; + str = (char*)malloc(length + 1); + if(!str) CERROR_BREAK(decoder->error, 9939); /*alloc fail*/ + + str[length] = 0; + for(i = 0; i < length; i++) str[i] = data[string2_begin + i]; + + decoder->error = LodePNG_Text_add(&decoder->infoPng.text, key, str); + + break; + } + + free(key); + free(str); + } + } + /*compressed text chunk (zTXt)*/ + else if(LodePNG_chunk_type_equals(chunk, "zTXt")) + { + if(decoder->settings.readTextChunks) + { + unsigned length, string2_begin; + char *key = 0; + ucvector decoded; + + ucvector_init(&decoded); + + while(!decoder->error) /*not really a while loop, only used to break on error*/ + { + for(length = 0; length < chunkLength && data[length] != 0; length++) ; + if(length + 2 >= chunkLength) CERROR_BREAK(decoder->error, 75); /*no null termination, corrupt?*/ + + key = (char*)malloc(length + 1); + if(!key) CERROR_BREAK(decoder->error, 9940); /*alloc fail*/ + + key[length] = 0; + for(i = 0; i < length; i++) key[i] = data[i]; + + if(data[length + 1] != 0) CERROR_BREAK(decoder->error, 72); /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) CERROR_BREAK(decoder->error, 75); /*no null termination, corrupt?*/ + + length = chunkLength - string2_begin; + decoder->error = LodePNG_zlib_decompress(&decoded.data, &decoded.size, + (unsigned char*)(&data[string2_begin]), + length, &decoder->settings.zlibsettings); + if(decoder->error) break; + ucvector_push_back(&decoded, 0); + + decoder->error = LodePNG_Text_add(&decoder->infoPng.text, key, (char*)decoded.data); + + break; + } + + free(key); + ucvector_cleanup(&decoded); + if(decoder->error) break; + } + } + /*international text chunk (iTXt)*/ + else if(LodePNG_chunk_type_equals(chunk, "iTXt")) + { + if(decoder->settings.readTextChunks) + { + unsigned length, begin, compressed; + char *key = 0, *langtag = 0, *transkey = 0; + ucvector decoded; + ucvector_init(&decoded); + + while(!decoder->error) /*not really a while loop, only used to break on error*/ + { + /*Quick check if the chunk length isn't too small. Even without check + it'd still fail with other error checks below if it's too short. This just gives a different error code.*/ + if(chunkLength < 5) CERROR_BREAK(decoder->error, 30); /*iTXt chunk too short*/ + + /*read the key*/ + for(length = 0; length < chunkLength && data[length] != 0; length++) ; + if(length + 2 >= chunkLength) CERROR_BREAK(decoder->error, 75); /*no null termination char found*/ + + key = (char*)malloc(length + 1); + if(!key) CERROR_BREAK(decoder->error, 9941); /*alloc fail*/ + + key[length] = 0; + for(i = 0; i < length; i++) key[i] = data[i]; + + /*read the compression method*/ + compressed = data[length + 1]; + if(data[length + 2] != 0) CERROR_BREAK(decoder->error, 72); /*the 0 byte indicating compression must be 0*/ + + /*read the langtag*/ + begin = length + 3; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; i++) length++; + if(begin + length + 1 >= chunkLength) CERROR_BREAK(decoder->error, 75); /*no null termination char found*/ + + langtag = (char*)malloc(length + 1); + if(!langtag) CERROR_BREAK(decoder->error, 9942); /*alloc fail*/ + + langtag[length] = 0; + for(i = 0; i < length; i++) langtag[i] = data[begin + i]; + + /*read the transkey*/ + begin += length + 1; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; i++) length++; + if(begin + length + 1 >= chunkLength) CERROR_BREAK(decoder->error, 75); /*no null termination, corrupt?*/ + + transkey = (char*)malloc(length + 1); + if(!transkey) CERROR_BREAK(decoder->error, 9943); /*alloc fail*/ + + transkey[length] = 0; + for(i = 0; i < length; i++) transkey[i] = data[begin + i]; + + /*read the actual text*/ + begin += length + 1; + if(begin > chunkLength) CERROR_BREAK(decoder->error, 75); /*no null termination, corrupt?*/ + + length = chunkLength - begin; + + if(compressed) + { + decoder->error = LodePNG_zlib_decompress(&decoded.data, &decoded.size, + (unsigned char*)(&data[begin]), + length, &decoder->settings.zlibsettings); + if(decoder->error) break; + ucvector_push_back(&decoded, 0); + } + else + { + if(!ucvector_resize(&decoded, length + 1)) CERROR_BREAK(decoder->error, 9944 /*alloc fail*/); + + decoded.data[length] = 0; + for(i = 0; i < length; i++) decoded.data[i] = data[begin + i]; + } + + decoder->error = LodePNG_IText_add(&decoder->infoPng.itext, key, langtag, transkey, (char*)decoded.data); + + break; + } + + free(key); + free(langtag); + free(transkey); + ucvector_cleanup(&decoded); + if(decoder->error) break; + } + } + else if(LodePNG_chunk_type_equals(chunk, "tIME")) + { + if(chunkLength != 7) CERROR_BREAK(decoder->error, 73); /*invalid tIME chunk size*/ + + decoder->infoPng.time_defined = 1; + decoder->infoPng.time.year = 256 * data[0] + data[+ 1]; + decoder->infoPng.time.month = data[2]; + decoder->infoPng.time.day = data[3]; + decoder->infoPng.time.hour = data[4]; + decoder->infoPng.time.minute = data[5]; + decoder->infoPng.time.second = data[6]; + } + else if(LodePNG_chunk_type_equals(chunk, "pHYs")) + { + if(chunkLength != 9) CERROR_BREAK(decoder->error, 74); /*invalid pHYs chunk size*/ + + decoder->infoPng.phys_defined = 1; + decoder->infoPng.phys_x = 16777216 * data[0] + 65536 * data[1] + 256 * data[2] + data[3]; + decoder->infoPng.phys_y = 16777216 * data[4] + 65536 * data[5] + 256 * data[6] + data[7]; + decoder->infoPng.phys_unit = data[8]; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + else /*it's not an implemented chunk type, so ignore it: skip over the data*/ + { + /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/ + if(LodePNG_chunk_critical(chunk)) CERROR_BREAK(decoder->error, 69); + + unknown = 1; +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + if(decoder->settings.rememberUnknownChunks) + { + LodePNG_UnknownChunks* unknown = &decoder->infoPng.unknown_chunks; + decoder->error = LodePNG_append_chunk(&unknown->data[critical_pos - 1], + &unknown->datasize[critical_pos - 1], chunk); + if(decoder->error) break; + } +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + } + + if(!decoder->settings.ignoreCrc && !unknown) /*check CRC if wanted, only on known chunk types*/ + { + if(LodePNG_chunk_check_crc(chunk)) CERROR_BREAK(decoder->error, 57); /*invalid CRC*/ + } + + if(!IEND) chunk = LodePNG_chunk_next_const(chunk); + } + + if(!decoder->error) + { + ucvector scanlines; + ucvector_init(&scanlines); + + /*maximum final image length is already reserved in the vector's length - this is not really necessary*/ + if(!ucvector_resize(&scanlines, ((decoder->infoPng.width * (decoder->infoPng.height + * LodePNG_InfoColor_getBpp(&decoder->infoPng.color) + 7)) / 8) + decoder->infoPng.height)) + { + decoder->error = 9945; /*alloc fail*/ + } + if(!decoder->error) + { + /*decompress with the Zlib decompressor*/ + decoder->error = LodePNG_zlib_decompress(&scanlines.data, &scanlines.size, idat.data, + idat.size, &decoder->settings.zlibsettings); + } + + if(!decoder->error) + { + ucvector outv; + ucvector_init(&outv); + if(!ucvector_resizev(&outv, (decoder->infoPng.height * decoder->infoPng.width + * LodePNG_InfoColor_getBpp(&decoder->infoPng.color) + 7) / 8, 0)) decoder->error = 9946; /*alloc fail*/ + if(!decoder->error) decoder->error = postProcessScanlines(outv.data, scanlines.data, &decoder->infoPng); + *out = outv.data; + *outsize = outv.size; + } + ucvector_cleanup(&scanlines); + } + + ucvector_cleanup(&idat); +} + +void LodePNG_Decoder_decode(LodePNG_Decoder* decoder, unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize) +{ + *out = 0; + *outsize = 0; + decodeGeneric(decoder, out, outsize, in, insize); + if(decoder->error) return; + if(!decoder->settings.color_convert || LodePNG_InfoColor_equal(&decoder->infoRaw.color, &decoder->infoPng.color)) + { + /*same color type, no copying or converting of data needed*/ + /*store the infoPng color settings on the infoRaw so that the infoRaw still reflects what colorType + the raw image has to the end user*/ + if(!decoder->settings.color_convert) + { + decoder->error = LodePNG_InfoColor_copy(&decoder->infoRaw.color, &decoder->infoPng.color); + if(decoder->error) return; + } + } + else + { + /*color conversion needed; sort of copy of the data*/ + unsigned char* data = *out; + + /*TODO: check if this works according to the statement in the documentation: "The converter can convert + from greyscale input color type, to 8-bit greyscale or greyscale with alpha"*/ + if(!(decoder->infoRaw.color.colorType == 2 || decoder->infoRaw.color.colorType == 6) + && !(decoder->infoRaw.color.bitDepth == 8)) + { + decoder->error = 56; /*unsupported color mode conversion*/ + return; + } + + *outsize = (decoder->infoPng.width * decoder->infoPng.height + * LodePNG_InfoColor_getBpp(&decoder->infoRaw.color) + 7) / 8; + *out = (unsigned char*)malloc(*outsize); + if(!(*out)) + { + decoder->error = 9947; /*alloc fail*/ + *outsize = 0; + } + else decoder->error = LodePNG_convert(*out, data, &decoder->infoRaw.color, &decoder->infoPng.color, + decoder->infoPng.width, decoder->infoPng.height); + free(data); + } +} + +unsigned LodePNG_decode(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, + size_t insize, unsigned colorType, unsigned bitDepth) +{ + unsigned error; + size_t dummy_size; + LodePNG_Decoder decoder; + LodePNG_Decoder_init(&decoder); + decoder.infoRaw.color.colorType = colorType; + decoder.infoRaw.color.bitDepth = bitDepth; + LodePNG_Decoder_decode(&decoder, out, &dummy_size, in, insize); + error = decoder.error; + *w = decoder.infoPng.width; + *h = decoder.infoPng.height; + LodePNG_Decoder_cleanup(&decoder); + return error; +} + +unsigned LodePNG_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) +{ + return LodePNG_decode(out, w, h, in, insize, 6, 8); +} + +unsigned LodePNG_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) +{ + return LodePNG_decode(out, w, h, in, insize, 2, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned LodePNG_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename, + unsigned colorType, unsigned bitDepth) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error; + error = LodePNG_loadFile(&buffer, &buffersize, filename); + if(!error) error = LodePNG_decode(out, w, h, buffer, buffersize, colorType, bitDepth); + free(buffer); + return error; +} + +unsigned LodePNG_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) +{ + return LodePNG_decode_file(out, w, h, filename, 6, 8); +} + +unsigned LodePNG_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) +{ + return LodePNG_decode_file(out, w, h, filename, 2, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void LodePNG_DecodeSettings_init(LodePNG_DecodeSettings* settings) +{ + settings->color_convert = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->readTextChunks = 1; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + settings->ignoreCrc = 0; +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + settings->rememberUnknownChunks = 0; +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + LodePNG_DecompressSettings_init(&settings->zlibsettings); +} + +void LodePNG_Decoder_init(LodePNG_Decoder* decoder) +{ + LodePNG_DecodeSettings_init(&decoder->settings); + LodePNG_InfoRaw_init(&decoder->infoRaw); + LodePNG_InfoPng_init(&decoder->infoPng); + decoder->error = 1; +} + +void LodePNG_Decoder_cleanup(LodePNG_Decoder* decoder) +{ + LodePNG_InfoRaw_cleanup(&decoder->infoRaw); + LodePNG_InfoPng_cleanup(&decoder->infoPng); +} + +void LodePNG_Decoder_copy(LodePNG_Decoder* dest, const LodePNG_Decoder* source) +{ + LodePNG_Decoder_cleanup(dest); + *dest = *source; + LodePNG_InfoRaw_init(&dest->infoRaw); + LodePNG_InfoPng_init(&dest->infoPng); + dest->error = LodePNG_InfoRaw_copy(&dest->infoRaw, &source->infoRaw); if(dest->error) return; + dest->error = LodePNG_InfoPng_copy(&dest->infoPng, &source->infoPng); if(dest->error) return; +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Encoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*chunkName must be string of 4 characters*/ +static unsigned addChunk(ucvector* out, const char* chunkName, const unsigned char* data, size_t length) +{ + unsigned error = LodePNG_create_chunk(&out->data, &out->size, (unsigned)length, chunkName, data); + if(error) return error; + out->allocsize = out->size; /*fix the allocsize again*/ + return 0; +} + +static void writeSignature(ucvector* out) +{ + /*8 bytes PNG signature, aka the magic bytes*/ + ucvector_push_back(out, 137); + ucvector_push_back(out, 80); + ucvector_push_back(out, 78); + ucvector_push_back(out, 71); + ucvector_push_back(out, 13); + ucvector_push_back(out, 10); + ucvector_push_back(out, 26); + ucvector_push_back(out, 10); +} + +static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h, unsigned bitDepth, + unsigned colorType, unsigned interlaceMethod) +{ + unsigned error = 0; + ucvector header; + ucvector_init(&header); + + LodePNG_add32bitInt(&header, w); /*width*/ + LodePNG_add32bitInt(&header, h); /*height*/ + ucvector_push_back(&header, (unsigned char)bitDepth); /*bit depth*/ + ucvector_push_back(&header, (unsigned char)colorType); /*color type*/ + ucvector_push_back(&header, 0); /*compression method*/ + ucvector_push_back(&header, 0); /*filter method*/ + ucvector_push_back(&header, interlaceMethod); /*interlace method*/ + + error = addChunk(out, "IHDR", header.data, header.size); + ucvector_cleanup(&header); + + return error; +} + +static unsigned addChunk_PLTE(ucvector* out, const LodePNG_InfoColor* info) +{ + unsigned error = 0; + size_t i; + ucvector PLTE; + ucvector_init(&PLTE); + for(i = 0; i < info->palettesize * 4; i++) + { + /*add all channels except alpha channel*/ + if(i % 4 != 3) ucvector_push_back(&PLTE, info->palette[i]); + } + error = addChunk(out, "PLTE", PLTE.data, PLTE.size); + ucvector_cleanup(&PLTE); + + return error; +} + +static unsigned addChunk_tRNS(ucvector* out, const LodePNG_InfoColor* info) +{ + unsigned error = 0; + size_t i; + ucvector tRNS; + ucvector_init(&tRNS); + if(info->colorType == 3) + { + /*add only alpha channel*/ + for(i = 0; i < info->palettesize; i++) ucvector_push_back(&tRNS, info->palette[4 * i + 3]); + } + else if(info->colorType == 0) + { + if(info->key_defined) + { + ucvector_push_back(&tRNS, (unsigned char)(info->key_r / 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_r % 256)); + } + } + else if(info->colorType == 2) + { + if(info->key_defined) + { + ucvector_push_back(&tRNS, (unsigned char)(info->key_r / 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_r % 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_g / 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_g % 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_b / 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_b % 256)); + } + } + + error = addChunk(out, "tRNS", tRNS.data, tRNS.size); + ucvector_cleanup(&tRNS); + + return error; +} + +static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t datasize, + LodePNG_CompressSettings* zlibsettings) +{ + ucvector zlibdata; + unsigned error = 0; + + /*compress with the Zlib compressor*/ + ucvector_init(&zlibdata); + error = LodePNG_zlib_compress(&zlibdata.data, &zlibdata.size, data, datasize, zlibsettings); + if(!error) error = addChunk(out, "IDAT", zlibdata.data, zlibdata.size); + ucvector_cleanup(&zlibdata); + + return error; +} + +static unsigned addChunk_IEND(ucvector* out) +{ + unsigned error = 0; + error = addChunk(out, "IEND", 0, 0); + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring) +{ + unsigned error = 0; + size_t i; + ucvector text; + ucvector_init(&text); + for(i = 0; keyword[i] != 0; i++) ucvector_push_back(&text, (unsigned char)keyword[i]); + ucvector_push_back(&text, 0); + for(i = 0; textstring[i] != 0; i++) ucvector_push_back(&text, (unsigned char)textstring[i]); + error = addChunk(out, "tEXt", text.data, text.size); + ucvector_cleanup(&text); + + return error; +} + +static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* textstring, + LodePNG_CompressSettings* zlibsettings) +{ + unsigned error = 0; + ucvector data, compressed; + size_t i, textsize = strlen(textstring); + + ucvector_init(&data); + ucvector_init(&compressed); + for(i = 0; keyword[i] != 0; i++) ucvector_push_back(&data, (unsigned char)keyword[i]); + ucvector_push_back(&data, 0); /* 0 termination char*/ + ucvector_push_back(&data, 0); /*compression method: 0*/ + + error = LodePNG_zlib_compress(&compressed.data, &compressed.size, + (unsigned char*)textstring, textsize, zlibsettings); + if(!error) + { + for(i = 0; i < compressed.size; i++) ucvector_push_back(&data, compressed.data[i]); + error = addChunk(out, "zTXt", data.data, data.size); + } + + ucvector_cleanup(&compressed); + ucvector_cleanup(&data); + return error; +} + +static unsigned addChunk_iTXt(ucvector* out, unsigned compressed, const char* keyword, const char* langtag, + const char* transkey, const char* textstring, LodePNG_CompressSettings* zlibsettings) +{ + unsigned error = 0; + ucvector data, compressed_data; + size_t i, textsize = strlen(textstring); + + ucvector_init(&data); + + for(i = 0; keyword[i] != 0; i++) ucvector_push_back(&data, (unsigned char)keyword[i]); + ucvector_push_back(&data, 0); /*null termination char*/ + ucvector_push_back(&data, compressed ? 1 : 0); /*compression flag*/ + ucvector_push_back(&data, 0); /*compression method*/ + for(i = 0; langtag[i] != 0; i++) ucvector_push_back(&data, (unsigned char)langtag[i]); + ucvector_push_back(&data, 0); /*null termination char*/ + for(i = 0; transkey[i] != 0; i++) ucvector_push_back(&data, (unsigned char)transkey[i]); + ucvector_push_back(&data, 0); /*null termination char*/ + + if(compressed) + { + ucvector_init(&compressed_data); + error = LodePNG_zlib_compress(&compressed_data.data, &compressed_data.size, + (unsigned char*)textstring, textsize, zlibsettings); + if(!error) + { + for(i = 0; i < compressed_data.size; i++) ucvector_push_back(&data, compressed_data.data[i]); + for(i = 0; textstring[i] != 0; i++) ucvector_push_back(&data, (unsigned char)textstring[i]); + } + } + else /*not compressed*/ + { + for(i = 0; textstring[i] != 0; i++) ucvector_push_back(&data, (unsigned char)textstring[i]); + } + + if(!error) error = addChunk(out, "iTXt", data.data, data.size); + ucvector_cleanup(&data); + return error; +} + +static unsigned addChunk_bKGD(ucvector* out, const LodePNG_InfoPng* info) +{ + unsigned error = 0; + ucvector bKGD; + ucvector_init(&bKGD); + if(info->color.colorType == 0 || info->color.colorType == 4) + { + ucvector_push_back(&bKGD, (unsigned char)(info->background_r / 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_r % 256)); + } + else if(info->color.colorType == 2 || info->color.colorType == 6) + { + ucvector_push_back(&bKGD, (unsigned char)(info->background_r / 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_r % 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_g / 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_g % 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_b / 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_b % 256)); + } + else if(info->color.colorType == 3) + { + ucvector_push_back(&bKGD, (unsigned char)(info->background_r % 256)); /*palette index*/ + } + + error = addChunk(out, "bKGD", bKGD.data, bKGD.size); + ucvector_cleanup(&bKGD); + + return error; +} + +static unsigned addChunk_tIME(ucvector* out, const LodePNG_Time* time) +{ + unsigned error = 0; + unsigned char* data = (unsigned char*)malloc(7); + if(!data) return 9948; /*alloc fail*/ + data[0] = (unsigned char)(time->year / 256); + data[1] = (unsigned char)(time->year % 256); + data[2] = time->month; + data[3] = time->day; + data[4] = time->hour; + data[5] = time->minute; + data[6] = time->second; + error = addChunk(out, "tIME", data, 7); + free(data); + return error; +} + +static unsigned addChunk_pHYs(ucvector* out, const LodePNG_InfoPng* info) +{ + unsigned error = 0; + ucvector data; + ucvector_init(&data); + + LodePNG_add32bitInt(&data, info->phys_x); + LodePNG_add32bitInt(&data, info->phys_y); + ucvector_push_back(&data, info->phys_unit); + + error = addChunk(out, "pHYs", data.data, data.size); + ucvector_cleanup(&data); + + return error; +} + +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline, + size_t length, size_t bytewidth, unsigned char filterType) +{ + size_t i; + switch(filterType) + { + case 0: /*None*/ + for(i = 0; i < length; i++) out[i] = scanline[i]; + break; + case 1: /*Sub*/ + if(prevline) + { + for(i = 0; i < bytewidth; i++) out[i] = scanline[i]; + for(i = bytewidth; i < length ; i++) out[i] = scanline[i] - scanline[i - bytewidth]; + } + else + { + for(i = 0; i < bytewidth; i++) out[i] = scanline[i]; + for(i = bytewidth; i < length; i++) out[i] = scanline[i] - scanline[i - bytewidth]; + } + break; + case 2: /*Up*/ + if(prevline) + { + for(i = 0; i < length; i++) out[i] = scanline[i] - prevline[i]; + } + else + { + for(i = 0; i < length; i++) out[i] = scanline[i]; + } + break; + case 3: /*Average*/ + if(prevline) + { + for(i = 0; i < bytewidth; i++) out[i] = scanline[i] - prevline[i] / 2; + for(i = bytewidth; i < length; i++) out[i] = scanline[i] - ((scanline[i - bytewidth] + prevline[i]) / 2); + } + else + { + for(i = 0; i < bytewidth; i++) out[i] = scanline[i]; + for(i = bytewidth; i < length; i++) out[i] = scanline[i] - scanline[i - bytewidth] / 2; + } + break; + case 4: /*Paeth*/ + if(prevline) + { + /*paethPredictor(0, prevline[i], 0) is always prevline[i]*/ + for(i = 0; i < bytewidth; i++) out[i] = (scanline[i] - prevline[i]); + for(i = bytewidth; i < length; i++) + { + out[i] = (scanline[i] - paethPredictor(scanline[i - bytewidth], prevline[i], prevline[i - bytewidth])); + } + } + else + { + for(i = 0; i < bytewidth; i++) out[i] = scanline[i]; + /*paethPredictor(scanline[i - bytewidth], 0, 0) is always scanline[i - bytewidth]*/ + for(i = bytewidth; i < length; i++) out[i] = (scanline[i] - scanline[i - bytewidth]); + } + break; + default: return; /*unexisting filter type given*/ + } +} + +static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, + const LodePNG_InfoColor* info, const LodePNG_EncodeSettings* settings) +{ + /* + For PNG filter method 0 + out must be a buffer with as size: h + (w * h * bpp + 7) / 8, because there are + the scanlines with 1 extra byte per scanline + */ + + unsigned bpp = LodePNG_InfoColor_getBpp(info); + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = (w * bpp + 7) / 8; + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7) / 8; + const unsigned char* prevline = 0; + unsigned x, y; + unsigned error = 0; + + if(bpp == 0) return 31; /*error: invalid color type*/ + + if(!settings->bruteForceFilters) + { + /* + There is a heuristic called the minimum sum of absolute differences heuristic, suggested by the PNG standard: + * If the image type is Palette, or the bit depth is smaller than 8, then do not filter the image (i.e. + use fixed filtering, with the filter None). + * (The other case) If the image type is Grayscale or RGB (with or without Alpha), and the bit depth is + not smaller than 8, then use adaptive filtering heuristic as follows: independently for each row, apply + all five filters and select the filter that produces the smallest sum of absolute values per row. + */ + if(info->colorType == 3 || info->bitDepth < 8) /*None filtertype for everything*/ + { + for(y = 0; y < h; y++) + { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + const unsigned TYPE = 0; + out[outindex] = TYPE; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, TYPE); + prevline = &in[inindex]; + } + } + else /*adaptive filtering*/ + { + size_t sum[5]; + ucvector attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned type, bestType = 0; + + for(type = 0; type < 5; type++) ucvector_init(&attempt[type]); + for(type = 0; type < 5; type++) + { + if(!ucvector_resize(&attempt[type], linebytes)) ERROR_BREAK(9949 /*alloc fail*/); + } + + if(!error) + { + for(y = 0; y < h; y++) + { + /*try the 5 filter types*/ + for(type = 0; type < 5; type++) + { + filterScanline(attempt[type].data, &in[y * linebytes], prevline, linebytes, bytewidth, type); + + /*calculate the sum of the result*/ + sum[type] = 0; + /*note that not all pixels are checked to speed this up while still having probably the best choice*/ + for(x = 0; x < attempt[type].size; x+=3) + { + /*For differences, each byte should be treated as signed, values above 127 are negative + (converted to signed char). Filtertype 0 isn't a difference though, so use unsigned there. + This means filtertype 0 is almost never chosen, but that is justified.*/ + if(type == 0) sum[type] += (unsigned char)(attempt[type].data[x]); + else + { + signed char s = (signed char)(attempt[type].data[x]); + sum[type] += s < 0 ? -s : s; + } + } + + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum[type] < smallest) + { + bestType = type; + smallest = sum[type]; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x < linebytes; x++) out[y * (linebytes + 1) + 1 + x] = attempt[bestType].data[x]; + } + } + + for(type = 0; type < 5; type++) ucvector_cleanup(&attempt[type]); + } + } + else + { + /*brute force filter chooser. + deflate the scanline after every filter attempt to see which one deflates best. + This is very slow and gives only slightly smaller, sometimes even larger, result*/ + size_t size[5]; + ucvector attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest; + unsigned type = 0, bestType = 0; + unsigned char* dummy; + LodePNG_CompressSettings zlibsettings = settings->zlibsettings; + /*use fixed tree on the attempts so that the tree is not adapted to the filtertype on purpose, + to simulate the true case where the tree is the same for the whole image. Sometimes it gives + better result with dynamic tree anyway. Using the fixed tree sometimes gives worse, but in rare + cases better compression. It does make this a bit less slow, so it's worth doing this.*/ + zlibsettings.btype = 1; + for(type = 0; type < 5; type++) + { + ucvector_init(&attempt[type]); + ucvector_resize(&attempt[type], linebytes); /*todo: give error if resize failed*/ + } + for(y = 0; y < h; y++) /*try the 5 filter types*/ + { + for(type = 0; type < 5; type++) + { + unsigned testsize = attempt[type].size; + /*unsigned testsize = attempt[type].size / 8;*/ /*it already works good enough by testing a part of the row*/ + /*if(testsize == 0) testsize = attempt[type].size;*/ + + filterScanline(attempt[type].data, &in[y * linebytes], prevline, linebytes, bytewidth, type); + size[type] = 0; + dummy = 0; + LodePNG_zlib_compress(&dummy, &size[type], attempt[type].data, testsize, &zlibsettings); + free(dummy); + /*check if this is smallest size (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || size[type] < smallest) + { + bestType = type; + smallest = size[type]; + } + } + prevline = &in[y * linebytes]; + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x < linebytes; x++) out[y * (linebytes + 1) + 1 + x] = attempt[bestType].data[x]; + } + for(type = 0; type < 5; type++) ucvector_cleanup(&attempt[type]); + } + + return error; +} + +static void addPaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) +{ + /*The opposite of the removePaddingBits function + olinebits must be >= ilinebits*/ + unsigned y; + size_t diff = olinebits - ilinebits; + size_t obp = 0, ibp = 0; /*bit pointers*/ + for(y = 0; y < h; y++) + { + size_t x; + for(x = 0; x < ilinebits; x++) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + /*obp += diff; --> no, fill in some value in the padding bits too, to avoid + "Use of uninitialised value of size ###" warning from valgrind*/ + for(x = 0; x < diff; x++) setBitOfReversedStream(&obp, out, 0); + } +} + +static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + /*Note: this function works on image buffers WITHOUT padding bits at end of scanlines with non-multiple-of-8 + bit amounts, only between reduced images is padding*/ + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) + { + for(i = 0; i < 7; i++) + { + unsigned x, y, b; + size_t bytewidth = bpp / 8; + for(y = 0; y < passh[i]; y++) + for(x = 0; x < passw[i]; x++) + { + size_t pixelinstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + size_t pixeloutstart = passstart[i] + (y * passw[i] + x) * bytewidth; + for(b = 0; b < bytewidth; b++) + { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } + else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ + { + for(i = 0; i < 7; i++) + { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; y++) + for(x = 0; x < passw[i]; x++) + { + ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + obp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + for(b = 0; b < bpp; b++) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +/*out must be buffer big enough to contain uncompressed IDAT chunk data, and in must contain the full image. +return value is error**/ +static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned char* in, + const LodePNG_InfoPng* infoPng, const LodePNG_EncodeSettings* settings) +{ + /* + This function converts the pure 2D image with the PNG's colortype, into filtered-padded-interlaced data. Steps: + *) if no Adam7: 1) add padding bits (= posible extra bits per scanline if bpp < 8) 2) filter + *) if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter + */ + unsigned bpp = LodePNG_InfoColor_getBpp(&infoPng->color); + unsigned w = infoPng->width; + unsigned h = infoPng->height; + unsigned error = 0; + + if(infoPng->interlaceMethod == 0) + { + *outsize = h + (h * ((w * bpp + 7) / 8)); /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)malloc(*outsize); + if(!(*out) && (*outsize)) error = 9950; /*alloc fail*/ + + if(!error) + { + /*non multiple of 8 bits per scanline, padding bits needed per scanline*/ + if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8) + { + ucvector padded; + ucvector_init(&padded); + if(!ucvector_resize(&padded, h * ((w * bpp + 7) / 8))) error = 9951; /*alloc fail*/ + if(!error) + { + addPaddingBits(padded.data, in, ((w * bpp + 7) / 8) * 8, w * bpp, h); + error = filter(*out, padded.data, w, h, &infoPng->color, settings); + } + ucvector_cleanup(&padded); + } + else + { + /*we can immediatly filter into the out buffer, no other steps needed*/ + error = filter(*out, in, w, h, &infoPng->color, settings); + } + } + } + else /*interlaceMethod is 1 (Adam7)*/ + { + unsigned char* adam7 = (unsigned char*)malloc((h * w * bpp + 7) / 8); + if(!adam7 && ((h * w * bpp + 7) / 8)) error = 9952; /*alloc fail*/ + + while(!error) /*not a real while loop, used to break out to cleanup to avoid a goto*/ + { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + *outsize = filter_passstart[7]; /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)malloc(*outsize); + if(!(*out) && (*outsize)) ERROR_BREAK(9953 /*alloc fail*/); + + Adam7_interlace(adam7, in, w, h, bpp); + + for(i = 0; i < 7; i++) + { + if(bpp < 8) + { + ucvector padded; + ucvector_init(&padded); + if(!ucvector_resize(&padded, h * ((w * bpp + 7) / 8))) error = 9954; /*alloc fail*/ + if(!error) + { + addPaddingBits(&padded.data[padded_passstart[i]], &adam7[passstart[i]], + ((passw[i] * bpp + 7) / 8) * 8, passw[i] * bpp, passh[i]); + error = filter(&(*out)[filter_passstart[i]], &padded.data[padded_passstart[i]], + passw[i], passh[i], &infoPng->color, settings); + } + + ucvector_cleanup(&padded); + } + else + { + error = filter(&(*out)[filter_passstart[i]], &adam7[padded_passstart[i]], + passw[i], passh[i], &infoPng->color, settings); + } + } + + break; + } + + free(adam7); + } + + return error; +} + +/*palette must have 4 * palettesize bytes allocated, and given in format RGBARGBARGBARGBA...*/ +static unsigned isPaletteFullyOpaque(const unsigned char* palette, size_t palettesize) +{ + size_t i; + for(i = 0; i < palettesize; i++) + { + if(palette[4 * i + 3] != 255) return 0; + } + return 1; +} + +/*this function checks if the input image given by the user has no transparent pixels*/ +static unsigned isFullyOpaque(const unsigned char* image, unsigned w, unsigned h, const LodePNG_InfoColor* info) +{ + /*TODO: When the user specified a color key for the input image, then this function must + also check for pixels that are the same as the color key and treat those as transparent.*/ + + unsigned i, numpixels = w * h; + if(info->colorType == 6) + { + if(info->bitDepth == 8) + { + for(i = 0; i < numpixels; i++) + { + if(image[i * 4 + 3] != 255) return 0; + } + } + else + { + for(i = 0; i < numpixels; i++) + { + if(image[i * 8 + 6] != 255 || image[i * 8 + 7] != 255) return 0; + } + } + return 1; /*no single pixel with alpha channel other than 255 found*/ + } + else if(info->colorType == 4) + { + if(info->bitDepth == 8) + { + for(i = 0; i < numpixels; i++) + { + if(image[i * 2 + 1] != 255) return 0; + } + } + else + { + for(i = 0; i < numpixels; i++) + { + if(image[i * 4 + 2] != 255 || image[i * 4 + 3] != 255) return 0; + } + } + return 1; /*no single pixel with alpha channel other than 255 found*/ + } + else if(info->colorType == 3) + { + /*when there's a palette, we could check every pixel for translucency, + but much quicker is to just check the palette*/ + return(isPaletteFullyOpaque(info->palette, info->palettesize)); + } + + return 0; /*color type that isn't supported by this function yet, so assume there is transparency to be safe*/ +} + +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS +static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize) +{ + unsigned char* inchunk = data; + while((size_t)(inchunk - data) < datasize) + { + unsigned error = LodePNG_append_chunk(&out->data, &out->size, inchunk); + if(error) return error; /*error: not enough memory*/ + out->allocsize = out->size; /*fix the allocsize again*/ + inchunk = LodePNG_chunk_next(inchunk); + } + return 0; +} +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + +void LodePNG_Encoder_encode(LodePNG_Encoder* encoder, unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h) +{ + LodePNG_InfoPng info; + ucvector outv; + unsigned char* data = 0; /*uncompressed version of the IDAT chunk data*/ + size_t datasize = 0; + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + encoder->error = 0; + + /*UNSAFE copy to avoid having to cleanup! but we will only change primitive parameters, + and not invoke the cleanup function nor touch the palette's buffer so we use it safely*/ + info = encoder->infoPng; + info.width = w; + info.height = h; + + if(encoder->settings.autoLeaveOutAlphaChannel && isFullyOpaque(image, w, h, &encoder->infoRaw.color)) + { + /*go to a color type without alpha channel*/ + if(info.color.colorType == 6) info.color.colorType = 2; + else if(info.color.colorType == 4) info.color.colorType = 0; + } + + if(encoder->settings.zlibsettings.windowSize > 32768) + { + encoder->error = 60; /*error: windowsize larger than allowed*/ + return; + } + if(encoder->settings.zlibsettings.btype > 2) + { + encoder->error = 61; /*error: unexisting btype*/ + return; + } + if(encoder->infoPng.interlaceMethod > 1) + { + encoder->error = 71; /*error: unexisting interlace mode*/ + return; + } + /*error: unexisting color type given*/ + if((encoder->error = checkColorValidity(info.color.colorType, info.color.bitDepth))) return; + /*error: unexisting color type given*/ + if((encoder->error = checkColorValidity(encoder->infoRaw.color.colorType, encoder->infoRaw.color.bitDepth))) return; + + if(!LodePNG_InfoColor_equal(&encoder->infoRaw.color, &info.color)) + { + unsigned char* converted; + size_t size = (w * h * LodePNG_InfoColor_getBpp(&info.color) + 7) / 8; + + if((info.color.colorType != 6 && info.color.colorType != 2) || (info.color.bitDepth != 8)) + { + encoder->error = 59; /*for the output image, only these types are supported*/ + return; + } + converted = (unsigned char*)malloc(size); + if(!converted && size) encoder->error = 9955; /*alloc fail*/ + if(!encoder->error) + { + encoder->error = LodePNG_convert(converted, image, &info.color, &encoder->infoRaw.color, w, h); + } + if(!encoder->error) preProcessScanlines(&data, &datasize, converted, &info, &encoder->settings); + free(converted); + } + else preProcessScanlines(&data, &datasize, image, &info, &encoder->settings); + + ucvector_init(&outv); + while(!encoder->error) /*while only executed once, to break on error*/ + { +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + size_t i; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*write signature and chunks*/ + writeSignature(&outv); + /*IHDR*/ + addChunk_IHDR(&outv, w, h, info.color.bitDepth, info.color.colorType, info.interlaceMethod); +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + /*unknown chunks between IHDR and PLTE*/ + if(info.unknown_chunks.data[0]) + { + encoder->error = addUnknownChunks(&outv, info.unknown_chunks.data[0], info.unknown_chunks.datasize[0]); + if(encoder->error) break; + } +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + /*PLTE*/ + if(info.color.colorType == 3) + { + if(info.color.palettesize == 0 || info.color.palettesize > 256) + { + encoder->error = 68; /*invalid palette size*/ + break; + } + addChunk_PLTE(&outv, &info.color); + } + if(encoder->settings.force_palette && (info.color.colorType == 2 || info.color.colorType == 6)) + { + if(info.color.palettesize == 0 || info.color.palettesize > 256) + { + encoder->error = 68; /*invalid palette size*/ + break; + } + addChunk_PLTE(&outv, &info.color); + } + /*tRNS*/ + if(info.color.colorType == 3 && !isPaletteFullyOpaque(info.color.palette, info.color.palettesize)) + { + addChunk_tRNS(&outv, &info.color); + } + if((info.color.colorType == 0 || info.color.colorType == 2) && info.color.key_defined) + { + addChunk_tRNS(&outv, &info.color); + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*bKGD (must come between PLTE and the IDAt chunks*/ + if(info.background_defined) addChunk_bKGD(&outv, &info); + /*pHYs (must come before the IDAT chunks)*/ + if(info.phys_defined) addChunk_pHYs(&outv, &info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + /*unknown chunks between PLTE and IDAT*/ + if(info.unknown_chunks.data[1]) + { + encoder->error = addUnknownChunks(&outv, info.unknown_chunks.data[1], info.unknown_chunks.datasize[1]); + if(encoder->error) break; + } +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + /*IDAT (multiple IDAT chunks must be consecutive)*/ + encoder->error = addChunk_IDAT(&outv, data, datasize, &encoder->settings.zlibsettings); + if(encoder->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*tIME*/ + if(info.time_defined) addChunk_tIME(&outv, &info.time); + /*tEXt and/or zTXt*/ + for(i = 0; i < info.text.num; i++) + { + if(strlen(info.text.keys[i]) > 79) + { + encoder->error = 66; /*text chunk too large*/ + break; + } + if(strlen(info.text.keys[i]) < 1) + { + encoder->error = 67; /*text chunk too small*/ + break; + } + if(encoder->settings.text_compression) + addChunk_zTXt(&outv, info.text.keys[i], info.text.strings[i], &encoder->settings.zlibsettings); + else + addChunk_tEXt(&outv, info.text.keys[i], info.text.strings[i]); + } + /*LodePNG version id in text chunk*/ + if(encoder->settings.add_id) + { + unsigned alread_added_id_text = 0; + for(i = 0; i < info.text.num; i++) + { + if(!strcmp(info.text.keys[i], "LodePNG")) + { + alread_added_id_text = 1; + break; + } + } + if(alread_added_id_text == 0) + addChunk_tEXt(&outv, "LodePNG", VERSION_STRING); /*it's shorter as tEXt than as zTXt chunk*/ + } + /*iTXt*/ + for(i = 0; i < info.itext.num; i++) + { + if(strlen(info.itext.keys[i]) > 79) + { + encoder->error = 66; /*text chunk too large*/ + break; + } + if(strlen(info.itext.keys[i]) < 1) + { + encoder->error = 67; /*text chunk too small*/ + break; + } + addChunk_iTXt(&outv, encoder->settings.text_compression, + info.itext.keys[i], info.itext.langtags[i], info.itext.transkeys[i], info.itext.strings[i], + &encoder->settings.zlibsettings); + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + /*unknown chunks between IDAT and IEND*/ + if(info.unknown_chunks.data[2]) + { + encoder->error = addUnknownChunks(&outv, info.unknown_chunks.data[2], info.unknown_chunks.datasize[2]); + if(encoder->error) break; + } +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + /*IEND*/ + addChunk_IEND(&outv); + + break; /*this isn't really a while loop; no error happened so break out now!*/ + } + + free(data); + /*instead of cleaning the vector up, give it to the output*/ + *out = outv.data; + *outsize = outv.size; +} + +unsigned LodePNG_encode(unsigned char** out, size_t* outsize, const unsigned char* image, + unsigned w, unsigned h, unsigned colorType, unsigned bitDepth) +{ + unsigned error; + LodePNG_Encoder encoder; + LodePNG_Encoder_init(&encoder); + encoder.infoRaw.color.colorType = colorType; + encoder.infoRaw.color.bitDepth = bitDepth; + encoder.infoPng.color.colorType = colorType; + encoder.infoPng.color.bitDepth = bitDepth; + LodePNG_Encoder_encode(&encoder, out, outsize, image, w, h); + error = encoder.error; + LodePNG_Encoder_cleanup(&encoder); + return error; +} + +unsigned LodePNG_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) +{ + return LodePNG_encode(out, outsize, image, w, h, 6, 8); +} + +unsigned LodePNG_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) +{ + return LodePNG_encode(out, outsize, image, w, h, 2, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned LodePNG_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h, + unsigned colorType, unsigned bitDepth) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error = LodePNG_encode(&buffer, &buffersize, image, w, h, colorType, bitDepth); + if(!error) error = LodePNG_saveFile(buffer, buffersize, filename); + free(buffer); + return error; +} + +unsigned LodePNG_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) +{ + return LodePNG_encode_file(filename, image, w, h, 6, 8); +} + +unsigned LodePNG_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) +{ + return LodePNG_encode_file(filename, image, w, h, 2, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void LodePNG_EncodeSettings_init(LodePNG_EncodeSettings* settings) +{ + LodePNG_CompressSettings_init(&settings->zlibsettings); + settings->bruteForceFilters = 0; + settings->autoLeaveOutAlphaChannel = 1; + settings->force_palette = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->add_id = 1; + settings->text_compression = 0; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +void LodePNG_Encoder_init(LodePNG_Encoder* encoder) +{ + LodePNG_EncodeSettings_init(&encoder->settings); + LodePNG_InfoPng_init(&encoder->infoPng); + LodePNG_InfoRaw_init(&encoder->infoRaw); + encoder->error = 1; +} + +void LodePNG_Encoder_cleanup(LodePNG_Encoder* encoder) +{ + LodePNG_InfoPng_cleanup(&encoder->infoPng); + LodePNG_InfoRaw_cleanup(&encoder->infoRaw); +} + +void LodePNG_Encoder_copy(LodePNG_Encoder* dest, const LodePNG_Encoder* source) +{ + LodePNG_Encoder_cleanup(dest); + *dest = *source; + LodePNG_InfoPng_init(&dest->infoPng); + LodePNG_InfoRaw_init(&dest->infoRaw); + dest->error = LodePNG_InfoPng_copy(&dest->infoPng, &source->infoPng); + if(dest->error) return; + dest->error = LodePNG_InfoRaw_copy(&dest->infoRaw, &source->infoRaw); + if(dest->error) return; +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT + +/* +This returns the description of a numerical error code in English. This is also +the documentation of all the error codes. +*/ +const char* LodePNG_error_text(unsigned code) +{ + switch(code) + { + case 0: return "no error, everything went ok"; + case 1: return "nothing done yet"; /*the Encoder/Decoder has done nothing yet, error checking makes no sense yet*/ + case 10: return "end of input memory reached without huffman end code"; /*while huffman decoding*/ + case 11: return "error in code tree made it jump outside of huffman tree"; /*while huffman decoding*/ + case 13: return "problem while processing dynamic deflate block"; + case 14: return "problem while processing dynamic deflate block"; + case 15: return "problem while processing dynamic deflate block"; + case 16: return "unexisting code while processing dynamic deflate block"; + case 17: return "end of out buffer memory reached while inflating"; + case 18: return "invalid distance code while inflating"; + case 19: return "end of out buffer memory reached while inflating"; + case 20: return "invalid deflate block BTYPE encountered while decoding"; + case 21: return "NLEN is not ones complement of LEN in a deflate block"; + + /*end of out buffer memory reached while inflating: + This can happen if the inflated deflate data is longer than the amount of bytes required to fill up + all the pixels of the image, given the color depth and image dimensions. Something that doesn't + happen in a normal, well encoded, PNG image.*/ + case 22: return "end of out buffer memory reached while inflating"; + + case 23: return "end of in buffer memory reached while inflating"; + case 24: return "invalid FCHECK in zlib header"; + case 25: return "invalid compression method in zlib header"; + case 26: return "FDICT encountered in zlib header while it's not used for PNG"; + case 27: return "PNG file is smaller than a PNG header"; + /*Checks the magic file header, the first 8 bytes of the PNG file*/ + case 28: return "incorrect PNG signature, it's no PNG or corrupted"; + case 29: return "first chunk is not the header chunk"; + case 30: return "chunk length too large, chunk broken off at end of file"; + case 31: return "illegal PNG color type or bpp"; + case 32: return "illegal PNG compression method"; + case 33: return "illegal PNG filter method"; + case 34: return "illegal PNG interlace method"; + case 35: return "chunk length of a chunk is too large or the chunk too small"; + case 36: return "illegal PNG filter type encountered"; + case 37: return "illegal bit depth for this color type given"; + case 38: return "the palette is too big"; /*more than 256 colors*/ + case 39: return "more palette alpha values given in tRNS chunk than there are colors in the palette"; + case 40: return "tRNS chunk has wrong size for greyscale image"; + case 41: return "tRNS chunk has wrong size for RGB image"; + case 42: return "tRNS chunk appeared while it was not allowed for this color type"; + case 43: return "bKGD chunk has wrong size for palette image"; + case 44: return "bKGD chunk has wrong size for greyscale image"; + case 45: return "bKGD chunk has wrong size for RGB image"; + /*Is the palette too small?*/ + case 46: return "a value in indexed image is larger than the palette size (bitdepth = 8)"; + /*Is the palette too small?*/ + case 47: return "a value in indexed image is larger than the palette size (bitdepth < 8)"; + /*the input data is empty, maybe a PNG file doesn't exist or is in the wrong path*/ + case 48: return "empty input or file doesn't exist"; + case 49: return "jumped past memory while generating dynamic huffman tree"; + case 50: return "jumped past memory while generating dynamic huffman tree"; + case 51: return "jumped past memory while inflating huffman block"; + case 52: return "jumped past memory while inflating"; + case 53: return "size of zlib data too small"; + + /*jumped past tree while generating huffman tree, this could be when the + tree will have more leaves than symbols after generating it out of the + given lenghts. They call this an oversubscribed dynamic bit lengths tree in zlib.*/ + case 55: return "jumped past tree while generating huffman tree"; + + case 56: return "given output image colorType or bitDepth not supported for color conversion"; + case 57: return "invalid CRC encountered (checking CRC can be disabled)"; + case 58: return "invalid ADLER32 encountered (checking ADLER32 can be disabled)"; + case 59: return "conversion to unexisting color mode or color mode conversion not supported"; + case 60: return "invalid window size given in the settings of the encoder (must be 0-32768)"; + case 61: return "invalid BTYPE given in the settings of the encoder (only 0, 1 and 2 are allowed)"; + /*LodePNG leaves the choice of RGB to greyscale conversion formula to the user.*/ + case 62: return "conversion from RGB to greyscale not supported"; + case 63: return "length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk"; /*(2^31-1)*/ + /*this would result in the inability of a deflated block to ever contain an end code. It must be at least 1.*/ + case 64: return "the length of the END symbol 256 in the Huffman tree is 0"; + case 66: return "the length of a text chunk keyword given to the encoder is longer than the maximum of 79 bytes"; + case 67: return "the length of a text chunk keyword given to the encoder is smaller than the minimum of 1 byte"; + case 68: return "tried to encode a PLTE chunk with a palette that has less than 1 or more than 256 colors"; + case 69: return "unknown chunk type with 'critical' flag encountered by the decoder"; + case 71: return "unexisting interlace mode given to encoder (must be 0 or 1)"; + case 72: return "while decoding, unexisting compression method encountering in zTXt or iTXt chunk (it must be 0)"; + case 73: return "invalid tIME chunk size"; + case 74: return "invalid pHYs chunk size"; + /*length could be wrong, or data chopped off*/ + case 75: return "no null termination char found while decoding text chunk"; + case 76: return "iTXt chunk too short to contain required bytes"; + case 77: return "integer overflow in buffer size"; + case 78: return "failed to open file for reading"; /*file doesn't exist or couldn't be opened for reading*/ + case 79: return "failed to open file for writing"; + case 80: return "tried creating a tree of 0 symbols"; + case 81: return "lazy matching at pos 0 is impossible"; + default: ; /*nothing to do here, checks for other error values are below*/ + } + + if(code >= 9900 && code <= 9999) return "memory allocation failed"; + + return "unknown error code"; +} + +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of PNG related code. Begin of C++ wrapper. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef __cplusplus +namespace LodePNG +{ +#ifdef LODEPNG_COMPILE_DISK + void loadFile(std::vector& buffer, const std::string& filename) + { + std::ifstream file(filename.c_str(), std::ios::in|std::ios::binary|std::ios::ate); + + /*get filesize*/ + std::streamsize size = 0; + if(file.seekg(0, std::ios::end).good()) size = file.tellg(); + if(file.seekg(0, std::ios::beg).good()) size -= file.tellg(); + + /*read contents of the file into the vector*/ + buffer.resize(size_t(size)); + if(size > 0) file.read((char*)(&buffer[0]), size); + } + + /*write given buffer to the file, overwriting the file, it doesn't append to it.*/ + void saveFile(const std::vector& buffer, const std::string& filename) + { + std::ofstream file(filename.c_str(), std::ios::out|std::ios::binary); + file.write(buffer.empty() ? 0 : (char*)&buffer[0], std::streamsize(buffer.size())); + } +#endif //LODEPNG_COMPILE_DISK + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER + unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNG_DecompressSettings& settings) + { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = LodePNG_zlib_decompress(&buffer, &buffersize, in, insize, &settings); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + free(buffer); + } + return error; + } + + unsigned decompress(std::vector& out, const std::vector& in, + const LodePNG_DecompressSettings& settings) + { + return decompress(out, in.empty() ? 0 : &in[0], in.size(), settings); + } +#endif //LODEPNG_COMPILE_DECODER + +#ifdef LODEPNG_COMPILE_ENCODER + unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNG_CompressSettings& settings) + { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = LodePNG_zlib_compress(&buffer, &buffersize, in, insize, &settings); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + free(buffer); + } + return error; + } + + unsigned compress(std::vector& out, const std::vector& in, + const LodePNG_CompressSettings& settings) + { + return compress(out, in.empty() ? 0 : &in[0], in.size(), settings); + } +#endif //LODEPNG_COMPILE_ENCODER +#endif //LODEPNG_COMPILE_ZLIB + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_DECODER + Decoder::Decoder() + { + LodePNG_Decoder_init(this); + } + + Decoder::~Decoder() + { + LodePNG_Decoder_cleanup(this); + } + + void Decoder::operator=(const LodePNG_Decoder& other) + { + LodePNG_Decoder_copy(this, &other); + } + + bool Decoder::hasError() const + { + return error != 0; + } + unsigned Decoder::getError() const + { + return error; + } + + unsigned Decoder::getWidth() const + { + return infoPng.width; + } + + unsigned Decoder::getHeight() const + { + return infoPng.height; + } + + unsigned Decoder::getBpp() + { + return LodePNG_InfoColor_getBpp(&infoPng.color); + } + + unsigned Decoder::getChannels() + { + return LodePNG_InfoColor_getChannels(&infoPng.color); + } + + unsigned Decoder::isGreyscaleType() + { + return LodePNG_InfoColor_isGreyscaleType(&infoPng.color); + } + + unsigned Decoder::isAlphaType() + { + return LodePNG_InfoColor_isAlphaType(&infoPng.color); + } + + void Decoder::decode(std::vector& out, const unsigned char* in, size_t insize) + { + unsigned char* buffer; + size_t buffersize; + LodePNG_Decoder_decode(this, &buffer, &buffersize, in, insize); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + free(buffer); + } + } + + void Decoder::decode(std::vector& out, const std::vector& in) + { + decode(out, in.empty() ? 0 : &in[0], in.size()); + } + + void Decoder::inspect(const unsigned char* in, size_t insize) + { + LodePNG_Decoder_inspect(this, in, insize); + } + + void Decoder::inspect(const std::vector& in) + { + inspect(in.empty() ? 0 : &in[0], in.size()); + } + + const LodePNG_DecodeSettings& Decoder::getSettings() const + { + return settings; + } + + LodePNG_DecodeSettings& Decoder::getSettings() + { + return settings; + } + + void Decoder::setSettings(const LodePNG_DecodeSettings& settings) + { + this->settings = settings; + } + + const LodePNG_InfoPng& Decoder::getInfoPng() const + { + return infoPng; + } + + LodePNG_InfoPng& Decoder::getInfoPng() + { + return infoPng; + } + + void Decoder::setInfoPng(const LodePNG_InfoPng& info) + { + error = LodePNG_InfoPng_copy(&this->infoPng, &info); + } + + void Decoder::swapInfoPng(LodePNG_InfoPng& info) + { + LodePNG_InfoPng_swap(&this->infoPng, &info); + } + + const LodePNG_InfoRaw& Decoder::getInfoRaw() const + { + return infoRaw; + } + + LodePNG_InfoRaw& Decoder::getInfoRaw() + { + return infoRaw; + } + + void Decoder::setInfoRaw(const LodePNG_InfoRaw& info) + { + error = LodePNG_InfoRaw_copy(&this->infoRaw, &info); + } + +#endif //LODEPNG_COMPILE_DECODER + + /* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER + + Encoder::Encoder() + { + LodePNG_Encoder_init(this); + } + + Encoder::~Encoder() + { + LodePNG_Encoder_cleanup(this); + } + + void Encoder::operator=(const LodePNG_Encoder& other) + { + LodePNG_Encoder_copy(this, &other); + } + + bool Encoder::hasError() const + { + return error != 0; + } + + unsigned Encoder::getError() const + { + return error; + } + + void Encoder::encode(std::vector& out, const unsigned char* image, unsigned w, unsigned h) + { + unsigned char* buffer; + size_t buffersize; + LodePNG_Encoder_encode(this, &buffer, &buffersize, image, w, h); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + free(buffer); + } + } + + void Encoder::encode(std::vector& out, const std::vector& image, + unsigned w, unsigned h) + { + encode(out, image.empty() ? 0 : &image[0], w, h); + } + + void Encoder::clearPalette() + { + LodePNG_InfoColor_clearPalette(&infoPng.color); + } + + void Encoder::addPalette(unsigned char r, unsigned char g, unsigned char b, unsigned char a) + { + error = LodePNG_InfoColor_addPalette(&infoPng.color, r, g, b, a); + } + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + void Encoder::clearText() + { + LodePNG_Text_clear(&infoPng.text); + } + + void Encoder::addText(const std::string& key, const std::string& str) + { + error = LodePNG_Text_add(&infoPng.text, key.c_str(), str.c_str()); + } + + void Encoder::clearIText() + { + LodePNG_IText_clear(&infoPng.itext); + } + + void Encoder::addIText(const std::string& key, const std::string& langtag, + const std::string& transkey, const std::string& str) + { + error = LodePNG_IText_add(&infoPng.itext, key.c_str(), langtag.c_str(), transkey.c_str(), str.c_str()); + } +#endif //LODEPNG_COMPILE_ANCILLARY_CHUNKS + + const LodePNG_EncodeSettings& Encoder::getSettings() const + { + return settings; + } + + LodePNG_EncodeSettings& Encoder::getSettings() + { + return settings; + } + + void Encoder::setSettings(const LodePNG_EncodeSettings& settings) + { + this->settings = settings; + } + + const LodePNG_InfoPng& Encoder::getInfoPng() const + { + return infoPng; + } + + LodePNG_InfoPng& Encoder::getInfoPng() + { + return infoPng; + } + + void Encoder::setInfoPng(const LodePNG_InfoPng& info) + { + error = LodePNG_InfoPng_copy(&this->infoPng, &info); + } + + void Encoder::swapInfoPng(LodePNG_InfoPng& info) + { + LodePNG_InfoPng_swap(&this->infoPng, &info); + } + + const LodePNG_InfoRaw& Encoder::getInfoRaw() const + { + return infoRaw; + } + + LodePNG_InfoRaw& Encoder::getInfoRaw() + { + return infoRaw; + } + + void Encoder::setInfoRaw(const LodePNG_InfoRaw& info) + { + error = LodePNG_InfoRaw_copy(&this->infoRaw, &info); + } +#endif //LODEPNG_COMPILE_ENCODER + + /* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DECODER + + unsigned decode(std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, + size_t insize, unsigned colorType, unsigned bitDepth) + { + Decoder decoder; + decoder.getInfoRaw().color.colorType = colorType; + decoder.getInfoRaw().color.bitDepth = bitDepth; + decoder.decode(out, in, insize); + w = decoder.getWidth(); + h = decoder.getHeight(); + return decoder.getError(); + } + + unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, unsigned colorType, unsigned bitDepth) + { + return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned)in.size(), colorType, bitDepth); + } + +#ifdef LODEPNG_COMPILE_DISK + unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename, + unsigned colorType, unsigned bitDepth) + { + std::vector buffer; + loadFile(buffer, filename); + return decode(out, w, h, buffer, colorType, bitDepth); + } +#endif //LODEPNG_COMPILE_DECODER +#endif //LODEPNG_COMPILE_DISK + +#ifdef LODEPNG_COMPILE_ENCODER + + unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, + unsigned colorType, unsigned bitDepth) + { + Encoder encoder; + encoder.getInfoRaw().color.colorType = colorType; + encoder.getInfoRaw().color.bitDepth = bitDepth; + encoder.getInfoPng().color.colorType = colorType; + encoder.getInfoPng().color.bitDepth = bitDepth; + encoder.encode(out, in, w, h); + return encoder.getError(); + } + + unsigned encode(std::vector& out, const std::vector& in, unsigned w, unsigned h, + unsigned colorType, unsigned bitDepth) + { + return encode(out, in.empty() ? 0 : &in[0], w, h, colorType, bitDepth); + } + +#ifdef LODEPNG_COMPILE_DISK + unsigned encode(const std::string& filename, const unsigned char* in, unsigned w, unsigned h, + unsigned colorType, unsigned bitDepth) + { + std::vector buffer; + Encoder encoder; + encoder.getInfoRaw().color.colorType = colorType; + encoder.getInfoRaw().color.bitDepth = bitDepth; + encoder.encode(buffer, in, w, h); + if(!encoder.hasError()) saveFile(buffer, filename); + return encoder.getError(); + } + + unsigned encode(const std::string& filename, const std::vector& in, unsigned w, unsigned h, + unsigned colorType, unsigned bitDepth) + { + return encode(filename, in.empty() ? 0 : &in[0], w, h, colorType, bitDepth); + } +#endif //LODEPNG_COMPILE_DISK +#endif //LODEPNG_COMPILE_ENCODER +#endif //LODEPNG_COMPILE_PNG +} //namespace LodePNG +#endif /*__cplusplus C++ RAII wrapper*/ diff --git a/tests/lodepng.h b/tests/lodepng.h new file mode 100644 index 00000000..64ec2e31 --- /dev/null +++ b/tests/lodepng.h @@ -0,0 +1,1893 @@ +/* +LodePNG version 20110908 + +Copyright (c) 2005-2011 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef LODEPNG_H +#define LODEPNG_H + +#include /*for size_t*/ + +#ifdef __cplusplus +#include +#include +#endif /*__cplusplus*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* Code Sections */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +The following #defines are used to create code sections. They can be disabled +to disable code sections, which can give faster compile time and smaller binary. +*/ + +/*deflate&zlib encoder and deflate&zlib decoder. +If this is disabled, you need to implement the dummy LodePNG_zlib_compress and +LodePNG_zlib_decompress functions in the #else belonging to this #define in the +source file.*/ +#define LODEPNG_COMPILE_ZLIB +/*png encoder and png decoder*/ +#define LODEPNG_COMPILE_PNG +/*deflate&zlib decoder and png decoder*/ +#define LODEPNG_COMPILE_DECODER +/*deflate&zlib encoder and png encoder*/ +#define LODEPNG_COMPILE_ENCODER +/*the optional built in harddisk file loading and saving functions*/ +#define LODEPNG_COMPILE_DISK +/*any code or struct datamember related to chunks other than IHDR, IDAT, PLTE, tRNS, IEND*/ +#define LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*handling of unknown chunks*/ +#define LODEPNG_COMPILE_UNKNOWN_CHUNKS +/*ability to convert error numerical codes to English text string*/ +#define LODEPNG_COMPILE_ERROR_TEXT + +/* ////////////////////////////////////////////////////////////////////////// */ +/* Simple Functions */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +This are the simple C and C++ functions, which cover basic usage. +Further on in the header file are the more advanced functions allowing custom behaviour. +*/ + +#ifdef LODEPNG_COMPILE_PNG + +/*constants for the PNG color types. "LCT" = "LodePNG Color Type"*/ +#define LCT_GREY 0 /*PNG color type greyscale*/ +#define LCT_RGB 2 /*PNG color type RGB*/ +#define LCT_PALETTE 3 /*PNG color type palette*/ +#define LCT_GREY_ALPHA 4 /*PNG color type greyscale with alpha*/ +#define LCT_RGBA 6 /*PNG color type RGB with alpha*/ + +#ifdef LODEPNG_COMPILE_DECODER +/* +Converts PNG data in memory to raw pixel data. +out: Output parameter. Pointer to buffer that will contain the raw pixel data. + Its size is w * h * (bytes per pixel), bytes per pixel depends on colorType and bitDepth. + Must be freed after usage with free(*out). +w: Output parameter. Pointer to width of pixel data. +h: Output parameter. Pointer to height of pixel data. +in: Memory buffer with the PNG file. +insize: size of the in buffer. +colorType: the desired color type for the raw output image. See explanation on PNG color types. +bitDepth: the desired bit depth for the raw output image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned LodePNG_decode(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize, unsigned colorType, unsigned bitDepth); + +/*Same as LodePNG_decode, but uses colorType = 6 and bitDepth = 8 by default (32-bit RGBA)*/ +unsigned LodePNG_decode32(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +/*Same as LodePNG_decode, but uses colorType = 2 and bitDepth = 8 by default (24-bit RGB)*/ +unsigned LodePNG_decode24(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_DISK +/* +Load PNG from disk, from file with given name. +out: Output parameter. Pointer to buffer that will contain the raw pixel data. + Its size is w * h * (bytes per pixel), bytes per pixel depends on colorType and bitDepth. + Must be freed after usage with free(*out). +w: Output parameter. Pointer to width of pixel data. +h: Output parameter. Pointer to height of pixel data. +filename: Path on disk of the PNG file. +colorType: the desired color type for the raw output image. See explanation on PNG color types. +bitDepth: the desired bit depth for the raw output image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned LodePNG_decode_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename, unsigned colorType, unsigned bitDepth); + +/*Same as LodePNG_decode_file, but uses colorType = 6 and bitDepth = 8 by default (32-bit RGBA)*/ +unsigned LodePNG_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename); + +/*Same as LodePNG_decode_file, but uses colorType = 2 and bitDepth = 8 by default (24-bit RGB)*/ +unsigned LodePNG_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename); + + +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Converts raw pixel data into a PNG image in memory. The colorType and bitDepth + of the output PNG image cannot be chosen, they are automatically determined + by the colorType, bitDepth and content of the input pixel data. +out: Output parameter. Pointer to buffer that will contain the raw pixel data. + Must be freed after usage with free(*out). +outsize: Output parameter. Pointer to the size in bytes of the out buffer. +image: The raw pixel data to encode. The size of this buffer should be + w * h * (bytes per pixel), bytes per pixel depends on colorType and bitDepth. +w: width of the raw pixel data in pixels. +h: height of the raw pixel data in pixels. +colorType: the color type of the raw input image. See explanation on PNG color types. +bitDepth: the bit depth of the raw input image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned LodePNG_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, unsigned colorType, unsigned bitDepth); + +/*Same as LodePNG_encode, but uses colorType = 6 and bitDepth = 8 by default (32-bit RGBA).*/ +unsigned LodePNG_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h); + +/*Same as LodePNG_encode, but uses colorType = 2 and bitDepth = 8 by default (24-bit RGB).*/ +unsigned LodePNG_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DISK + +/* +Converts raw pixel data into a PNG file on disk. Same as LodePNG_encode, but +outputs to disk instead of memory buffer. +filename: path to file on disk to write the PNG image to. +image: The raw pixel data to encode. The size of this buffer should be + w * h * (bytes per pixel), bytes per pixel depends on colorType and bitDepth. +w: width of the raw pixel data in pixels. +h: height of the raw pixel data in pixels. +colorType: the color type of the raw input image. See explanation on PNG color types. +bitDepth: the bit depth of the raw input image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned LodePNG_encode_file(const char* filename, const unsigned char* image, + unsigned w, unsigned h, unsigned colorType, unsigned bitDepth); + +/*Same as LodePNG_encode_file, but uses colorType = 6 and bitDepth = 8 by default (32-bit RGBS).*/ +unsigned LodePNG_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h); + +/*Same as LodePNG_encode_file, but uses colorType = 2 and bitDepth = 8 by default (24-bit RGB).*/ +unsigned LodePNG_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef __cplusplus +namespace LodePNG +{ + +#ifdef LODEPNG_COMPILE_DECODER + /* + Converts PNG data in memory to raw pixel data. + out: Output parameter, std::vector containing the raw pixel data. Its size + will be w * h * (bytes per pixel), where bytes per pixel is 4 if the default + colorType=6 and bitDepth=8 is used. The pixels are 32-bit RGBA bit in that case. + w: Output parameter, width of the image in pixels. + h: Output parameter, height of the image in pixels. + in: Memory buffer with the PNG file. + insize: size of the in buffer. + colorType: the desired color type for the raw output image. See explanation on PNG color types. + bitDepth: the desired bit depth for the raw output image. See explanation on PNG color types. + Return value: LodePNG error code (0 means no error). + */ + unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const unsigned char* in, size_t insize, unsigned colorType = LCT_RGBA, unsigned bitDepth = 8); + + /* + Same as the decode function that takes a unsigned char buffer, but instead of giving + a pointer and a size, this takes the input buffer as an std::vector. + */ + unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, unsigned colorType = LCT_RGBA, unsigned bitDepth = 8); +#ifdef LODEPNG_COMPILE_DISK + /* + Converts PNG file from disk to raw pixel data in memory. + out: Output parameter, std::vector containing the raw pixel data. Its size + will be w * h * (bytes per pixel), where bytes per pixel is 4 if the default + colorType=6 and bitDepth=8 is used. The pixels are 32-bit RGBA bit in that case. + w: Output parameter, width of the image in pixels. + h: Output parameter, height of the image in pixels. + filename: Path to PNG file on disk. + colorType: the desired color type for the raw output image. See explanation on PNG color types. + bitDepth: the desired bit depth for the raw output image. See explanation on PNG color types. + Return value: LodePNG error code (0 means no error). + */ + unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::string& filename, unsigned colorType = LCT_RGBA, unsigned bitDepth = 8); +#endif //LODEPNG_COMPILE_DISK +#endif //LODEPNG_COMPILE_DECODER + +#ifdef LODEPNG_COMPILE_ENCODER + /* + Converts 32-bit RGBA raw pixel data into a PNG image in memory. + out: Output parameter, std::vector containing the PNG image data. + in: Memory buffer with raw pixel data. The size of this buffer should be + w * h * (bytes per pixel), With the default colorType=6 and bitDepth=8, bytes + per pixel should be 4 and the data is a 32-bit RGBA pixel buffer. + w: Width of the image in pixels. + h: Height of the image in pixels. + colorType: the color type of the raw input image. See explanation on PNG color types. + bitDepth: the bit depth of the raw input image. See explanation on PNG color types. + Return value: LodePNG error code (0 means no error). + */ + unsigned encode(std::vector& out, const unsigned char* in, + unsigned w, unsigned h, unsigned colorType = LCT_RGBA, unsigned bitDepth = 8); + + /* + Same as the encode function that takes a unsigned char buffer, but instead of giving + a pointer and a size, this takes the input buffer as an std::vector. + */ + unsigned encode(std::vector& out, const std::vector& in, + unsigned w, unsigned h, unsigned colorType = LCT_RGBA, unsigned bitDepth = 8); +#ifdef LODEPNG_COMPILE_DISK + /* + Converts 32-bit RGBA raw pixel data into a PNG file on disk. + filename: Path to the file to write the PNG image to. + in: Memory buffer with raw pixel data. The size of this buffer should be + w * h * (bytes per pixel), With the default colorType=6 and bitDepth=8, bytes + per pixel should be 4 and the data is a 32-bit RGBA pixel buffer. + w: Width of the image in pixels. + h: Height of the image in pixels. + colorType: the color type of the raw input image. See explanation on PNG color types. + bitDepth: the bit depth of the raw input image. See explanation on PNG color types. + Return value: LodePNG error code (0 means no error). + */ + unsigned encode(const std::string& filename, const unsigned char* in, + unsigned w, unsigned h, unsigned colorType = LCT_RGBA, unsigned bitDepth = 8); + + /* + Same as the encode function that takes a unsigned char buffer, but instead of giving + a pointer and a size, this takes the input buffer as an std::vector. + */ + unsigned encode(const std::string& filename, const std::vector& in, + unsigned w, unsigned h, unsigned colorType = LCT_RGBA, unsigned bitDepth = 8); +#endif //LODEPNG_COMPILE_DISK +#endif //LODEPNG_COMPILE_ENCODER +} //namespace LodePNG +#endif /*__cplusplus*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/* +Returns a textual description of the error code, in English. The +numerical value of the code itself is not included in this description. +*/ +const char* LodePNG_error_text(unsigned code); +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* Inflate & Deflate Setting Structs */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +These structs contain settings for the decompression and compression of the +PNG files. Typically you won't need these directly. +*/ + +#ifdef LODEPNG_COMPILE_DECODER +typedef struct LodePNG_DecompressSettings +{ + unsigned ignoreAdler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/ +} LodePNG_DecompressSettings; + +extern const LodePNG_DecompressSettings LodePNG_defaultDecompressSettings; +void LodePNG_DecompressSettings_init(LodePNG_DecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Compression settings. Tweaking these settings tweaks the balance between +speed and compression ratio. +*/ +typedef struct LodePNG_CompressSettings /*deflate = compress*/ +{ + /*LZ77 related settings*/ + unsigned btype; /*the block type for LZ (0, 1, 2 or 3, see zlib standard). Should be 2 for proper compression.*/ + unsigned useLZ77; /*whether or not to use LZ77. Should be 1 for proper compression.*/ + unsigned windowSize; /*the maximum is 32768, higher gives more compression but is slower. Typical value: 2048.*/ +} LodePNG_CompressSettings; + +extern const LodePNG_CompressSettings LodePNG_defaultCompressSettings; +void LodePNG_CompressSettings_init(LodePNG_CompressSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_PNG +/* ////////////////////////////////////////////////////////////////////////// */ +/* PNG and Raw Image Information Structs */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Info about the color type of an image. +The same LodePNG_InfoColor struct is used for both the PNG and raw image type, +even though they are two totally different things. +*/ +typedef struct LodePNG_InfoColor +{ + /*header (IHDR)*/ + unsigned colorType; /*color type, see PNG standard or documentation further in this header file*/ + unsigned bitDepth; /*bits per sample, see PNG standard or documentation further in this header file*/ + + /* + palette (PLTE and tRNS) + + This is a dynamically allocated unsigned char array with the colors of the palette, including alpha. + The value palettesize indicates the amount of colors in the palette. + The allocated size of the buffer is 4 * palettesize bytes, in order RGBARGBARGBA... + + When encoding a PNG, to store your colors in the palette of the LodePNG_InfoRaw, first use + LodePNG_InfoColor_clearPalette, then for each color use LodePNG_InfoColor_addPalette. + + If you encode an image without alpha with palette, don't forget to put value 255 in each A byte of the palette. + + When decoding, by default you can ignore this palette, since LodePNG already + fills the palette colors in the pixels of the raw RGBA output. + */ + unsigned char* palette; /*palette in RGBARGBA... order*/ + size_t palettesize; /*palette size in number of colors (amount of bytes is 4 * palettesize)*/ + + /* + transparent color key (tRNS) + This color uses the same bit depth as the bitDepth value in this struct, which can be 1-bit to 16-bit. + For greyscale PNGs, r, g and b will all 3 be set to the same. + + When decoding, by default you can ignore this information, since LodePNG sets + pixels with this key to transparent already in the raw RGBA output. + */ + unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/ + unsigned key_r; /*red/greyscale component of color key*/ + unsigned key_g; /*green component of color key*/ + unsigned key_b; /*blue component of color key*/ +} LodePNG_InfoColor; + +/*init, cleanup and copy functions to use with this struct*/ +void LodePNG_InfoColor_init(LodePNG_InfoColor* info); +void LodePNG_InfoColor_cleanup(LodePNG_InfoColor* info); +/*return value is error code (0 means no error)*/ +unsigned LodePNG_InfoColor_copy(LodePNG_InfoColor* dest, const LodePNG_InfoColor* source); + +void LodePNG_InfoColor_clearPalette(LodePNG_InfoColor* info); +/*add 1 color to the palette*/ +unsigned LodePNG_InfoColor_addPalette(LodePNG_InfoColor* info, unsigned char r, + unsigned char g, unsigned char b, unsigned char a); + +/*additional color info*/ + +/*get the total amount of bits per pixel, based on colorType and bitDepth in the struct*/ +unsigned LodePNG_InfoColor_getBpp(const LodePNG_InfoColor* info); +/*get the amount of color channels used, based on colorType in the struct. +If a palette is used, it counts as 1 channel.*/ +unsigned LodePNG_InfoColor_getChannels(const LodePNG_InfoColor* info); +/*is it a greyscale type? (only colorType 0 or 4)*/ +unsigned LodePNG_InfoColor_isGreyscaleType(const LodePNG_InfoColor* info); +/*has it got an alpha channel? (only colorType 2 or 6)*/ +unsigned LodePNG_InfoColor_isAlphaType(const LodePNG_InfoColor* info); +/*has it got a palette? (only colorType 3)*/ +unsigned LodePNG_InfoColor_isPaletteType(const LodePNG_InfoColor* info); +/*only returns true if there is a palette and there is a value in the palette with alpha < 255. +Loops through the palette to check this.*/ +unsigned LodePNG_InfoColor_hasPaletteAlpha(const LodePNG_InfoColor* info); + +/* +Check if the given color info indicates the possibility of having non-opaque pixels in the PNG image. +Returns true if the image can have translucent or invisible pixels (it still be opaque if it doesn't use such pixels). +Returns false if the image can only have opaque pixels. +In detail, it returns true only if it's a color type with alpha, or has a palette with non-opaque values, +or if "key_defined" is true. +*/ +unsigned LodePNG_InfoColor_canHaveAlpha(const LodePNG_InfoColor* info); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/* +The information of a Time chunk in PNG. +To make the encoder add a time chunk, set time_defined to 1 and fill in +the correct values in all the time parameters. LodePNG will not fill the current +time in these values itself, all it does is copy them over into the chunk bytes. +*/ +typedef struct LodePNG_Time +{ + unsigned year; /*2 bytes used (0-65535)*/ + unsigned char month; /*1-12*/ + unsigned char day; /*1-31*/ + unsigned char hour; /*0-23*/ + unsigned char minute; /*0-59*/ + unsigned char second; /*0-60 (to allow for leap seconds)*/ +} LodePNG_Time; + +/* +Info about text chunks in a PNG file. The arrays can contain multiple keys +and strings. The amount of keys and strings is the same. The amount of strings +ends when the pointer to the string is a null pointer. + +They keyword of text chunks gives a short description what the actual text +represents. There are a few standard standard keywords recognised +by many programs: Title, Author, Description, Copyright, Creation Time, +Software, Disclaimer, Warning, Source, Comment. It's allowed to use other keys. + +A keyword is minimum 1 character and maximum 79 characters long. It's +discouraged to use a single line length longer than 79 characters for texts. +*/ +typedef struct LodePNG_Text /*non-international text*/ +{ + /*Don't allocate these text buffers yourself. Use the init/cleanup functions + correctly and use LodePNG_Text_add and LodePNG_Text_clear.*/ + size_t num; /*the amount of texts in these char** buffers (there may be more texts in itext)*/ + char** keys; /*the keyword of a text chunk (e.g. "Comment")*/ + char** strings; /*the actual text*/ +} LodePNG_Text; + +/*init, cleanup and copy functions to use with this struct*/ +void LodePNG_Text_init(LodePNG_Text* text); +void LodePNG_Text_cleanup(LodePNG_Text* text); +/*return value is error code (0 means no error)*/ +unsigned LodePNG_Text_copy(LodePNG_Text* dest, const LodePNG_Text* source); + +/*Use these functions instead of allocating the char**s manually*/ +void LodePNG_Text_clear(LodePNG_Text* text); /*use this to clear the texts again after you filled them in*/ +unsigned LodePNG_Text_add(LodePNG_Text* text, const char* key, const char* str); /*push back both texts at once*/ + +/* +Info about international text chunks in a PNG file. The arrays can contain multiple keys +and strings. The amount of keys, lengtags, transkeys and strings is the same. +The amount of strings ends when the pointer to the string is a null pointer. + +A keyword is minimum 1 character and maximum 79 characters long. It's +discouraged to use a single line length longer than 79 characters for texts. +*/ +typedef struct LodePNG_IText /*international text*/ +{ + /*Don't allocate these text buffers yourself. Use the init/cleanup functions + correctly and use LodePNG_IText_add and LodePNG_IText_clear.*/ + + /*the amount of international texts in this PNG*/ + size_t num; + /*the English keyword of the text chunk (e.g. "Comment")*/ + char** keys; + /*the language tag for this text's international language, ISO/IEC 646 string, e.g. ISO 639 language tag*/ + char** langtags; + /*keyword translated to the international language - UTF-8 string*/ + char** transkeys; + /*the actual international text - UTF-8 string*/ + char** strings; +} LodePNG_IText; + +/*init, cleanup and copy functions to use with this struct*/ +void LodePNG_IText_init(LodePNG_IText* text); +void LodePNG_IText_cleanup(LodePNG_IText* text); +/*return value is error code (0 means no error)*/ +unsigned LodePNG_IText_copy(LodePNG_IText* dest, const LodePNG_IText* source); + +/*Use these functions instead of allocating the char**s manually*/ +void LodePNG_IText_clear(LodePNG_IText* text); /*use this to clear the itexts again after you filled them in*/ +unsigned LodePNG_IText_add(LodePNG_IText* text, const char* key, const char* langtag, + const char* transkey, const char* str); /*push back the 4 texts of 1 chunk at once*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS +/* +Unknown chunks read from the PNG, or extra chunks the user wants to have added +in the encoded PNG. +*/ +typedef struct LodePNG_UnknownChunks +{ + /*There are 3 buffers, one for each position in the PNG where unknown chunks can appear + each buffer contains all unknown chunks for that position consecutively + The 3 buffers are the unknown chunks between certain critical chunks: + 0: IHDR-PLTE, 1: PLTE-IDAT, 2: IDAT-IEND + + Do not allocate or traverse this data yourself. Use the chunk traversing functions declared + later, such as LodePNG_chunk_next and LodePNG_append_chunk, to read/write this struct. + */ + unsigned char* data[3]; + size_t datasize[3]; /*size in bytes of the unknown chunks, given for protection*/ + +} LodePNG_UnknownChunks; + +/*init, cleanup and copy functions to use with this struct*/ +void LodePNG_UnknownChunks_init(LodePNG_UnknownChunks* chunks); +void LodePNG_UnknownChunks_cleanup(LodePNG_UnknownChunks* chunks); +/*return value is error code (0 means no error)*/ +unsigned LodePNG_UnknownChunks_copy(LodePNG_UnknownChunks* dest, const LodePNG_UnknownChunks* src); +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + +/* +Information about the PNG image, except pixels and sometimes except width and height. +*/ +typedef struct LodePNG_InfoPng +{ + /*header (IHDR), palette (PLTE) and transparency (tRNS)*/ + + /* + Note: width and height are only used as information of a decoded PNG image. When encoding one, you don't have + to specify width and height in an LodePNG_Info struct, but you give them as parameters of the encode function. + The rest of the LodePNG_Info struct IS used by the encoder though! + */ + unsigned width; /*width of the image in pixels (ignored by encoder, but filled in by decoder)*/ + unsigned height; /*height of the image in pixels (ignored by encoder, but filled in by decoder)*/ + unsigned compressionMethod; /*compression method of the original file. Always 0.*/ + unsigned filterMethod; /*filter method of the original file*/ + unsigned interlaceMethod; /*interlace method of the original file*/ + LodePNG_InfoColor color; /*color type and bits, palette and transparency of the PNG file*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /* + suggested background color (bKGD) + This color uses the same bit depth as the bitDepth value in this struct, which can be 1-bit to 16-bit. + + For greyscale PNGs, r, g and b will all 3 be set to the same. When encoding + the encoder writes the red one. For palette PNGs: When decoding, the RGB value + will be stored, not a palette index. But when encoding, specify the index of + the palette in background_r, the other two are then ignored. + + The decoder does not use this background color to edit the color of pixels. + */ + unsigned background_defined; /*is a suggested background color given?*/ + unsigned background_r; /*red component of suggested background color*/ + unsigned background_g; /*green component of suggested background color*/ + unsigned background_b; /*blue component of suggested background color*/ + + /*non-international text chunks (tEXt and zTXt)*/ + LodePNG_Text text; + + /*international text chunks (iTXt)*/ + LodePNG_IText itext; + + /*time chunk (tIME)*/ + unsigned char time_defined; /*if 0, no tIME chunk was or will be generated in the PNG image*/ + LodePNG_Time time; + + /*phys chunk (pHYs)*/ + unsigned phys_defined; /*if 0, there is no pHYs chunk and the values below are undefined, if 1 else there is one*/ + unsigned phys_x; /*pixels per unit in x direction*/ + unsigned phys_y; /*pixels per unit in y direction*/ + unsigned char phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + /*unknown chunks*/ + LodePNG_UnknownChunks unknown_chunks; +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ +} LodePNG_InfoPng; + +/*init, cleanup and copy functions to use with this struct*/ +void LodePNG_InfoPng_init(LodePNG_InfoPng* info); +void LodePNG_InfoPng_cleanup(LodePNG_InfoPng* info); +/*return value is error code (0 means no error)*/ +unsigned LodePNG_InfoPng_copy(LodePNG_InfoPng* dest, const LodePNG_InfoPng* source); + +/* +Contains user-chosen information about the raw image data, which is independent of the PNG image +With raw images, I mean the image data in the form of the simple raw buffer to which the +compressed PNG data is decoded, or from which a PNG image can be encoded. +*/ +typedef struct LodePNG_InfoRaw +{ + LodePNG_InfoColor color; /*color info of the raw image, note that the same struct as for PNG data is used.*/ +} LodePNG_InfoRaw; + +/*init, cleanup and copy functions to use with this struct*/ +void LodePNG_InfoRaw_init(LodePNG_InfoRaw* info); +void LodePNG_InfoRaw_cleanup(LodePNG_InfoRaw* info); +/*return value is error code (0 means no error)*/ +unsigned LodePNG_InfoRaw_copy(LodePNG_InfoRaw* dest, const LodePNG_InfoRaw* source); + +/* +Converts raw buffer from one color type to another color type, based on +LodePNG_InfoColor structs to describe the input and output color type. +See the reference manual at the end of this header file to see which color conversions are supported. +return value = LodePNG error code (0 if all went ok, an error if the conversion isn't supported) +The out buffer must have size (w * h * bpp + 7) / 8, where bpp is the bits per pixel +of the output color type (LodePNG_InfoColor_getBpp) +*/ +unsigned LodePNG_convert(unsigned char* out, const unsigned char* in, LodePNG_InfoColor* infoOut, + LodePNG_InfoColor* infoIn, unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* LodePNG Decoder */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Settings for the decoder. This contains settings for the PNG and the Zlib +decoder, but not the Info settings from the Info structs. +*/ +typedef struct LodePNG_DecodeSettings +{ + LodePNG_DecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/ + + unsigned ignoreCrc; /*ignore CRC checksums*/ + unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned readTextChunks; /*if false but rememberUnknownChunks is true, they're stored in the unknown chunks*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + /*store all bytes from unknown chunks in the InfoPng (off by default, useful for a png editor)*/ + unsigned rememberUnknownChunks; +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ +} LodePNG_DecodeSettings; + +void LodePNG_DecodeSettings_init(LodePNG_DecodeSettings* settings); + +/* +The LodePNG_Decoder struct has most input and output parameters the decoder uses, +such as the settings, the info of the PNG and the raw data, and the error. Only +the pixel buffer is not contained in this struct. +*/ +typedef struct LodePNG_Decoder +{ + LodePNG_DecodeSettings settings; /*the decoding settings*/ + LodePNG_InfoRaw infoRaw; /*specifies the format in which you would like to get the raw pixel buffer*/ + LodePNG_InfoPng infoPng; /*info of the PNG image obtained after decoding*/ + unsigned error; +} LodePNG_Decoder; + +/*init, cleanup and copy functions to use with this struct*/ +void LodePNG_Decoder_init(LodePNG_Decoder* decoder); +void LodePNG_Decoder_cleanup(LodePNG_Decoder* decoder); +void LodePNG_Decoder_copy(LodePNG_Decoder* dest, const LodePNG_Decoder* source); + +/* +Decode based on a LodePNG_Decoder. +This function allocates the out buffer and stores the size in *outsize. This buffer +needs to be freed after usage. +Other information about the PNG file, such as the size, colorType and extra chunks +are stored in the infoPng field of the LodePNG_Decoder. +*/ +void LodePNG_Decoder_decode(LodePNG_Decoder* decoder, unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize); + +/* +Read the PNG header, but not the actual data. This returns only the information +that is in the header chunk of the PNG, such as width, height and color type. The +information is placed in the infoPng field of the LodePNG_Decoder. +*/ +void LodePNG_Decoder_inspect(LodePNG_Decoder* decoder, const unsigned char* in, size_t insize); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* ////////////////////////////////////////////////////////////////////////// */ +/* LodePNG Encoder */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*Settings for the encoder.*/ +typedef struct LodePNG_EncodeSettings +{ + LodePNG_CompressSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/ + + /*Brute-force-search PNG filters by compressing each filter for each scanline. + This gives better compression, at the cost of being super slow. + Don't enable this for normal image saving, compression can take minutes + instead of seconds. This is experimental. If enabled, compression still isn't + as good as some other PNG encoders and optimizers, except for some images. + If you enable this, also consider setting zlibsettings.windowSize to 32768, and consider + using a less than 24-bit per pixel colorType if the image has <= 256 colors, for optimal compression. + Default: 0 (false)*/ + unsigned bruteForceFilters; + + /*automatically use color type without alpha instead of given one, if given image is opaque*/ + unsigned autoLeaveOutAlphaChannel; + /*force creating a PLTE chunk if colortype is 2 or 6 (= a suggested palette). + If colortype is 3, PLTE is _always_ created.*/ + unsigned force_palette; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*add LodePNG version as text chunk*/ + unsigned add_id; + /*encode text chunks as zTXt chunks instead of tEXt chunks, and use compression in iTXt chunks*/ + unsigned text_compression; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNG_EncodeSettings; + +void LodePNG_EncodeSettings_init(LodePNG_EncodeSettings* settings); + +/* +This struct has most input and output parameters the encoder uses, +such as the settings, the info of the PNG and the raw data, and the error. Only +the pixel buffer is not contained in this struct. +*/ +typedef struct LodePNG_Encoder +{ + /*compression settings of the encoder*/ + LodePNG_EncodeSettings settings; + /*the info specified by the user is not changed by the encoder. The encoder + will try to generate a PNG close to the given info.*/ + LodePNG_InfoPng infoPng; + /*put the properties of the input raw image in here*/ + LodePNG_InfoRaw infoRaw; + /*error value filled in if error happened, or 0 if all went ok*/ + unsigned error; +} LodePNG_Encoder; + +/*init, cleanup and copy functions to use with this struct*/ +void LodePNG_Encoder_init(LodePNG_Encoder* encoder); +void LodePNG_Encoder_cleanup(LodePNG_Encoder* encoder); +void LodePNG_Encoder_copy(LodePNG_Encoder* dest, const LodePNG_Encoder* source); + +/*This function allocates the out buffer with standard malloc and stores the size in *outsize.*/ +void LodePNG_Encoder_encode(LodePNG_Encoder* encoder, unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* Chunk Traversing Utilities */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +LodePNG_chunk functions: +These functions need as input a large enough amount of allocated memory. +These functions can be used on raw PNG data, but they are exposed in the API +because they are needed if you want to traverse the unknown chunks stored +in the LodePNG_UnknownChunks struct, or add new ones to it. +*/ + +/*get the length of the data of the chunk. Total chunk length has 12 bytes more.*/ +unsigned LodePNG_chunk_length(const unsigned char* chunk); + +/*puts the 4-byte type in null terminated string*/ +void LodePNG_chunk_type(char type[5], const unsigned char* chunk); + +/*check if the type is the given type*/ +unsigned char LodePNG_chunk_type_equals(const unsigned char* chunk, const char* type); + +/* +These functions get properties of PNG chunks gotten from capitalization of chunk +type name, as defined by the PNG standard. +*/ + +/* +properties of PNG chunks gotten from capitalization of chunk type name, as defined by the standard +0: ancillary chunk, 1: it's one of the critical chunk types +*/ +unsigned char LodePNG_chunk_critical(const unsigned char* chunk); + +/*0: public, 1: private*/ +unsigned char LodePNG_chunk_private(const unsigned char* chunk); + +/*0: the chunk is unsafe to copy, 1: the chunk is safe to copy*/ +unsigned char LodePNG_chunk_safetocopy(const unsigned char* chunk); + +/*get pointer to the data of the chunk*/ +unsigned char* LodePNG_chunk_data(unsigned char* chunk); + +/*get pointer to the data of the chunk*/ +const unsigned char* LodePNG_chunk_data_const(const unsigned char* chunk); + +/*returns 0 if the crc is correct, 1 if it's incorrect*/ +unsigned LodePNG_chunk_check_crc(const unsigned char* chunk); + +/*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/ +void LodePNG_chunk_generate_crc(unsigned char* chunk); + +/*iterate to next chunks. don't use on IEND chunk, as there is no next chunk then*/ +unsigned char* LodePNG_chunk_next(unsigned char* chunk); +const unsigned char* LodePNG_chunk_next_const(const unsigned char* chunk); + +/* +Appends chunk to the data in out. The given chunk should already have its chunk header. +The out variable and outlength are updated to reflect the new reallocated buffer. +Returns error code (0 if it went ok) +*/ +unsigned LodePNG_append_chunk(unsigned char** out, size_t* outlength, const unsigned char* chunk); + +/* +Appends new chunk to out. The chunk to append is given by giving its length, type +and data separately. The type is a 4-letter string. +The out variable and outlength are updated to reflect the new reallocated buffer. +Returne error code (0 if it went ok) +*/ +unsigned LodePNG_create_chunk(unsigned char** out, size_t* outlength, unsigned length, + const char* type, const unsigned char* data); +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ZLIB +/* ////////////////////////////////////////////////////////////////////////// */ +/* Zlib encoder and decoder */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +This zlib part can be used independently to zlib compress and decompress a +buffer. It cannot be used to create gzip files however, and it only supports the +part of zlib that is required for PNG, it does not support dictionaries. +*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Decompresses Zlib data. Reallocates the out buffer and appends the data. The +data must be according to the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage.*/ +unsigned LodePNG_zlib_decompress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, const LodePNG_DecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/*Compresses data with Zlib. Reallocates the out buffer and appends the data. +The data is output in the format of the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage.*/ +unsigned LodePNG_zlib_compress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, const LodePNG_CompressSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into buffer. The function allocates the out buffer, and +after usage you are responsible for freeing it. +out: output parameter, contains pointer to loaded buffer. +outsize: output parameter, size of the allocated out buffer +filename: the path to the file to load +return value: error code (0 means ok) +*/ +unsigned LodePNG_loadFile(unsigned char** out, size_t* outsize, const char* filename); + +/* +Save a file from buffer to disk. Warning, if it exists, this function overwrites +the file without warning! +buffer: the buffer to write +buffersize: size of the buffer to write +filename: the path to the file to save to +return value: error code (0 means ok) +*/ +unsigned LodePNG_saveFile(const unsigned char* buffer, size_t buffersize, const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ + +#ifdef __cplusplus +/* ////////////////////////////////////////////////////////////////////////// */ +/* LodePNG C++ wrapper */ +/* ////////////////////////////////////////////////////////////////////////// */ + +//The LodePNG C++ wrapper uses classes with handy constructors and destructors +//instead of manual init and cleanup functions, and uses std::vectors instead of +//manually allocated memory buffers. + +namespace LodePNG +{ +#ifdef LODEPNG_COMPILE_ZLIB +//The C++ wrapper for the zlib part +#ifdef LODEPNG_COMPILE_DECODER + //Zlib-decompress an unsigned char buffer + unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNG_DecompressSettings& settings = LodePNG_defaultDecompressSettings); + + //Zlib-decompress an std::vector + unsigned decompress(std::vector& out, const std::vector& in, + const LodePNG_DecompressSettings& settings = LodePNG_defaultDecompressSettings); +#endif //LODEPNG_COMPILE_DECODER + +#ifdef LODEPNG_COMPILE_ENCODER + //Zlib-compress an unsigned char buffer + unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNG_CompressSettings& settings = LodePNG_defaultCompressSettings); + + //Zlib-compress an std::vector + unsigned compress(std::vector& out, const std::vector& in, + const LodePNG_CompressSettings& settings = LodePNG_defaultCompressSettings); +#endif //LODEPNG_COMPILE_ENCODER +#endif //LODEPNG_COMPILE_ZLIB + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_DECODER + /* + Class to decode a PNG image. Before decoding, settings can be set and + after decoding, extra information about the PNG can be retrieved. + Extends from the C-struct LodePNG_Decoder to add constructors and destructors + to initialize/cleanup it automatically. Beware, no virtual destructor is used. + */ + class Decoder : public LodePNG_Decoder + { + public: + + Decoder(); + ~Decoder(); + void operator=(const LodePNG_Decoder& other); + + //decode PNG buffer to raw out buffer. Width and height can be retrieved with getWidth() and + //getHeight() and error should be checked with hasError() and getError() + void decode(std::vector& out, const unsigned char* in, size_t insize); + + //decode PNG buffer to raw out buffer. Width and height can be retrieved with getWidth() and + //getHeight() and error should be checked with hasError() and getError() + void decode(std::vector& out, const std::vector& in); + + //inspect functions: get only the info from the PNG header. The info can then be retrieved with + //the functions of this class. + void inspect(const unsigned char* in, size_t insize); + + //inspect functions: get only the info from the PNG header. The info can then be retrieved with + //the functions of this class. + void inspect(const std::vector& in); + + //error checking after decoding + bool hasError() const; + unsigned getError() const; + + //convenient access to some InfoPng parameters after decoding + unsigned getWidth() const; //width of image in pixels + unsigned getHeight() const; //height of image in pixels + unsigned getBpp(); //bits per pixel + unsigned getChannels(); //amount of channels + unsigned isGreyscaleType(); //is it a greyscale type? (colorType 0 or 4) + unsigned isAlphaType(); //has it an alpha channel? (colorType 2 or 6) + + //getters and setters for the decoding settings + const LodePNG_DecodeSettings& getSettings() const; + LodePNG_DecodeSettings& getSettings(); + void setSettings(const LodePNG_DecodeSettings& info); + + //getters and setters for the PNG image info, after decoding this describes information of the PNG image + const LodePNG_InfoPng& getInfoPng() const; + LodePNG_InfoPng& getInfoPng(); + void setInfoPng(const LodePNG_InfoPng& info); + void swapInfoPng(LodePNG_InfoPng& info); //faster than copying with setInfoPng + + //getters and setters for the raw image info, this determines in what format + //you get the pixel buffer from the decoder + const LodePNG_InfoRaw& getInfoRaw() const; + LodePNG_InfoRaw& getInfoRaw(); + void setInfoRaw(const LodePNG_InfoRaw& info); + }; +#endif //LODEPNG_COMPILE_DECODER + +#ifdef LODEPNG_COMPILE_ENCODER + /* + Class to encode a PNG image. Before encoding, settings can be set. + Extends from the C-struct LodePNG_Enoder to add constructors and destructors + to initialize/cleanup it automatically. Beware, no virtual destructor is used. + */ + class Encoder : public LodePNG_Encoder + { + public: + + Encoder(); + ~Encoder(); + void operator=(const LodePNG_Encoder& other); + + //encoding image to PNG buffer + void encode(std::vector& out, const unsigned char* image, unsigned w, unsigned h); + + //encoding image to PNG buffer + void encode(std::vector& out, const std::vector& image, unsigned w, unsigned h); + + //error checking after decoding + bool hasError() const; + unsigned getError() const; + + //convenient direct access to some parameters of the InfoPng + void clearPalette(); + void addPalette(unsigned char r, unsigned char g, unsigned char b, unsigned char a); //add 1 color to the palette +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + void clearText(); + void addText(const std::string& key, const std::string& str); //push back both texts at once + void clearIText(); + void addIText(const std::string& key, const std::string& langtag, + const std::string& transkey, const std::string& str); +#endif //LODEPNG_COMPILE_ANCILLARY_CHUNKS + + //getters and setters for the encoding settings + const LodePNG_EncodeSettings& getSettings() const; + LodePNG_EncodeSettings& getSettings(); + void setSettings(const LodePNG_EncodeSettings& info); + + //getters and setters for the PNG image info, this describes what color type + //and other settings the resulting PNG should have + const LodePNG_InfoPng& getInfoPng() const; + LodePNG_InfoPng& getInfoPng(); + void setInfoPng(const LodePNG_InfoPng& info); + void swapInfoPng(LodePNG_InfoPng& info); //faster than copying with setInfoPng + + //getters and setters for the raw image info, this describes how the encoder + //should interpret the input pixel buffer + const LodePNG_InfoRaw& getInfoRaw() const; + LodePNG_InfoRaw& getInfoRaw(); + void setInfoRaw(const LodePNG_InfoRaw& info); + }; +#endif //LODEPNG_COMPILE_ENCODER + +#ifdef LODEPNG_COMPILE_DISK + /* + Load a file from disk into an std::vector. If the vector is empty, then either + the file doesn't exist or is an empty file. + */ + void loadFile(std::vector& buffer, const std::string& filename); + + /* + Save the binary data in an std::vector to a file on disk. The file is overwritten + without warning. + */ + void saveFile(const std::vector& buffer, const std::string& filename); +#endif //LODEPNG_COMPILE_DISK +#endif //LODEPNG_COMPILE_PNG +} //namespace LodePNG +#endif /*end of __cplusplus wrapper*/ + +/* +TODO: +[.] test if there are no memory leaks or security exploits - done a lot but needs to be checked often +[.] check compatibility with vareous compilers - done but needs to be redone for every newer version +[ ] LZ77 encoder more like the one described in zlib - to make sure it's patentfree +[X] converting color to 16-bit per channel types +[ ] read all public PNG chunk types (but never let the color profile and gamma ones touch RGB values) +[ ] make sure encoder generates no chunks with size > (2^31)-1 +[ ] partial decoding (stream processing) +[ ] let the "isFullyOpaque" function check color keys and transparent palettes too +[X] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl" +[ ] don't stop decoding on errors like 69, 57, 58 (make warnings) +[ ] make option to choose if the raw image with non multiple of 8 bits per scanline should have padding bits or not +[ ] let the C++ wrapper catch exceptions coming from the standard library and return LodePNG error codes +*/ + +#endif /*LODEPNG_H inclusion guard*/ + +/* +LodePNG Documentation +--------------------- + +This documentations contains background information and examples. For the function +and class documentation, see the comments in the declarations above. + +0. table of contents +-------------------- + + 1. about + 1.1. supported features + 1.2. features not supported + 2. C and C++ version + 3. security + 4. decoding + 5. encoding + 6. color conversions + 6.1. PNG color types + 6.2. Default Behaviour of LodePNG + 6.3. Color Conversions + 6.4. More Notes + 7. error values + 8. chunks and PNG editing + 9. compiler support + 10. examples + 10.1. decoder C++ example + 10.2. encoder C++ example + 10.3. decoder C example + 11. changes + 12. contact information + + +1. about +-------- + +PNG is a file format to store raster images losslessly with good compression, +supporting different color types. It can be implemented in a patent-free way. + +LodePNG is a PNG codec according to the Portable Network Graphics (PNG) +Specification (Second Edition) - W3C Recommendation 10 November 2003. + +The specifications used are: + +*) Portable Network Graphics (PNG) Specification (Second Edition): + http://www.w3.org/TR/2003/REC-PNG-20031110 +*) RFC 1950 ZLIB Compressed Data Format version 3.3: + http://www.gzip.org/zlib/rfc-zlib.html +*) RFC 1951 DEFLATE Compressed Data Format Specification ver 1.3: + http://www.gzip.org/zlib/rfc-deflate.html + +The most recent version of LodePNG can currently be found at +http://members.gamedev.net/lode/projects/LodePNG/ + +LodePNG works both in C (ISO C90) and C++, with a C++ wrapper that adds +extra functionality. + +LodePNG exists out of two files: +-lodepng.h: the header file for both C and C++ +-lodepng.c(pp): give it the name lodepng.c or lodepng.cpp (or .cc) depending on your usage + +If you want to start using LodePNG right away without reading this doc, get the +files lodepng_examples.c or lodepng_examples.cpp to see how to use it in code, +or check the (smaller) examples in chapter 13 here. + +LodePNG is simple but only supports the basic requirements. To achieve +simplicity, the following design choices were made: There are no dependencies +on any external library. To decode PNGs, there's a Decoder struct or class that +can convert any PNG file data into an RGBA image buffer with a single function +call. To encode PNGs, there's an Encoder struct or class that can convert image +data into PNG file data with a single function call. To read and write files, +there are simple functions to convert the files to/from buffers in memory. + +This all makes LodePNG suitable for loading textures in games, demoscene +productions, saving a screenshot, images in programs that require them for simple +usage, ... It's less suitable for full fledged image editors, loading PNGs +over network (it requires all the image data to be available before decoding can +begin), life-critical systems, ... +LodePNG has a standards conformant decoder and encoder, and supports the ability +to make a somewhat conformant editor. + +1.1. supported features +----------------------- + +The following features are supported by the decoder: + +*) decoding of PNGs with any color type, bit depth and interlace mode, to a 24- or 32-bit color raw image, + or the same color type as the PNG +*) encoding of PNGs, from any raw image to 24- or 32-bit color, or the same color type as the raw image +*) Adam7 interlace and deinterlace for any color type +*) loading the image from harddisk or decoding it from a buffer from other sources than harddisk +*) support for alpha channels, including RGBA color model, translucent palettes and color keying +*) zlib decompression (inflate) +*) zlib compression (deflate) +*) CRC32 and ADLER32 checksums +*) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks. +*) the following chunks are supported (generated/interpreted) by both encoder and decoder: + IHDR: header information + PLTE: color palette + IDAT: pixel data + IEND: the final chunk + tRNS: transparency for palettized images + tEXt: textual information + zTXt: compressed textual information + iTXt: international textual information + bKGD: suggested background color + pHYs: physical dimensions + tIME: modification time + +1.2. features not supported +--------------------------- + +The following features are _not_ supported: + +*) some features needed to make a conformant PNG-Editor might be still missing. +*) partial loading/stream processing. All data must be available and is processed in one call. +*) The following public chunks are not supported but treated as unknown chunks by LodePNG + cHRM, gAMA, iCCP, sRGB, sBIT, hIST, sPLT + + +2. C and C++ version +-------------------- + +The C version uses buffers allocated with alloc that you need to free() +yourself. On top of that, you need to use init and cleanup functions for each +struct whenever using a struct from the C version to avoid exploits and memory leaks. + +The C++ version has constructors and destructors that take care of these things, +and uses std::vectors in the interface for storing data. + +Both the C and the C++ version are contained in this file! The C++ code depends on +the C code, the C code works on its own. + +These files work without modification for both C and C++ compilers because all the +additional C++ code is in "#ifdef __cplusplus" blocks that make C-compilers ignore +it, and the C code is made to compile both with strict ISO C90 and C++. + +To use the C++ version, you need to rename the source file to lodepng.cpp (instead +of lodepng.c), and compile it with a C++ compiler. + +To use the C version, you need to rename the source file to lodepng.c (instead +of lodepng.cpp), and compile it with a C compiler. + + +3. Security +----------- + +As with most software, even if carefully designed, it's always possible that +LodePNG may contain possible exploits. + +If you discover a possible exploit, please let me know, and it will be fixed. + +When using LodePNG, care has to be taken with the C version of LodePNG, as well as the C-style +structs when working with C++. The following conventions are used for all C-style structs: + +-if a struct has a corresponding init function, always call the init function when making a new one, to avoid exploits +-if a struct has a corresponding cleanup function, call it before the struct disappears to avoid memory leaks +-if a struct has a corresponding copy function, use the copy function instead of "=". + The destination must also be inited already! + +4. Decoding +----------- + +Decoding converts a PNG compressed image to a raw pixel buffer. + +Most documentation on using the decoder is at its declarations in the header +above. For C, simple decoding can be done with functions such as LodePNG_decode32, +and more advanced decoding can be done with the struct LodePNG_Decoder and its +functions. For C++, simple decoding can be done with the LodePNG::decode functions +and advanced decoding with the LodePNG::Decoder class. + +The Decoder contains 3 components: +*) LodePNG_InfoPng: it stores information about the PNG (the input) in an LodePNG_InfoPng struct, + don't modify this one yourself +*) Settings: you can specify a few other settings for the decoder to use +*) LodePNG_InfoRaw: here you can say what type of raw image (the output) you want to get + +Some of the parameters described below may be inside the sub-struct "LodePNG_InfoColor color". +In the C and C++ version, when using Info structs outside of the decoder or encoder, you need to use their +init and cleanup functions, but normally you use the ones in the decoder that are already handled +in the init and cleanup functions of the decoder itself. + +=LodePNG_InfoPng= + +This contains information such as the original color type of the PNG image, text +comments, suggested background color, etc... More details about the LodePNG_InfoPng struct +are at its declaration documentation. + +Because the dimensions of the image are important, there are shortcuts to get them in the +C++ version: use decoder.getWidth() and decoder.getHeight(). +In the C version, use decoder.infoPng.width and decoder.infoPng.height. + +=LodePNG_InfoRaw= + +In the LodePNG_InfoRaw struct of the Decoder, you can specify which color type you want +the resulting raw image to be. If this is different from the colorType of the +PNG, then the decoder will automatically convert the result to your LodePNG_InfoRaw +settings. Not all combinations of color conversions are supported though, see +a different section for information about the color modes and supported conversions. + +Palette of LodePNG_InfoRaw isn't used by the Decoder, when converting from palette color +to palette color, the values of the pixels are left untouched so that the colors +will change if the palette is different. Color key of LodePNG_InfoRaw is not used by the +Decoder. If setting color_convert is false then LodePNG_InfoRaw is completely ignored, +but it will be modified to match the color type of the PNG so will be overwritten. + +By default, 32-bit color is used for the result. + +=Settings= + +The Settings can be used to ignore the errors created by invalid CRC and Adler32 +chunks, and to disable the decoding of tEXt chunks. + +There's also a setting color_convert, true by default. If false, no conversion +is done, the resulting data will be as it was in the PNG (after decompression) +and you'll have to puzzle the colors of the pixels together yourself using the +color type information in the LodePNG_InfoPng. + + +5. Encoding +----------- + +Encoding converts a raw pixel buffer to a PNG compressed image. + +Most documentation on using the encoder is at its declarations in the header +above. For C, simple encoding can be done with functions such as LodePNG_encode32, +and more advanced decoding can be done with the struct LodePNG_Encoder and its +functions. For C++, simple encoding can be done with the LodePNG::encode functions +and advanced decoding with the LodePNG::Encoder class. + +Like the decoder, the encoder can also give errors. However it gives less errors +since the encoder input is trusted, the decoder input (a PNG image that could +be forged by anyone) is not trusted. + +Like the Decoder, the Encoder has 3 components: +*) LodePNG_InfoRaw: here you say what color type of the raw image (the input) has +*) Settings: you can specify a few settings for the encoder to use +*) LodePNG_InfoPng: the same LodePNG_InfoPng struct as created by the Decoder. For the encoder, +with this you specify how you want the PNG (the output) to be. + +Some of the parameters described below may be inside the sub-struct "LodePNG_InfoColor color". +In the C and C++ version, when using Info structs outside of the decoder or encoder, you need to use their +init and cleanup functions, but normally you use the ones in the encoder that are already handled +in the init and cleanup functions of the decoder itself. + +=LodePNG_InfoPng= + +The Decoder class stores information about the PNG image in an LodePNG_InfoPng object. With +the Encoder you can do the opposite: you give it an LodePNG_InfoPng object, and it'll try +to match the LodePNG_InfoPng you give as close as possible in the PNG it encodes. For +example in the LodePNG_InfoPng you can specify the color type you want to use, possible +tEXt chunks you want the PNG to contain, etc... For an explanation of all the +values in LodePNG_InfoPng see a further section. Not all PNG color types are supported +by the Encoder. + +The encoder will not always exactly match the LodePNG_InfoPng struct you give, +it tries as close as possible. Some things are ignored by the encoder. The width +and height of LodePNG_InfoPng are ignored as well, because instead the width and +height of the raw image you give in the input are used. In fact the encoder +currently uses only the following settings from it: +-colorType and bitDepth: the ones it supports +-text chunks, that you can add to the LodePNG_InfoPng with "addText" +-the color key, if applicable for the given color type +-the palette, if you encode to a PNG with colorType 3 +-the background color: it'll add a bKGD chunk to the PNG if one is given +-the interlaceMethod: None (0) or Adam7 (1) + +When encoding to a PNG with colorType 3, the encoder will generate a PLTE chunk. +If the palette contains any colors for which the alpha channel is not 255 (so +there are translucent colors in the palette), it'll add a tRNS chunk. + +=LodePNG_InfoRaw= + +You specify the color type of the raw image that you give to the input here, +including a possible transparent color key and palette you happen to be using in +your raw image data. + +By default, 32-bit color is assumed, meaning your input has to be in RGBA +format with 4 bytes (unsigned chars) per pixel. + +=Settings= + +The following settings are supported (some are in sub-structs): +*) autoLeaveOutAlphaChannel: when this option is enabled, when you specify a PNG +color type with alpha channel (not to be confused with the color type of the raw +image you specify!!), but the encoder detects that all pixels of the given image +are opaque, then it'll automatically use the corresponding type without alpha +channel, resulting in a smaller PNG image. +*) btype: the block type for LZ77. + 0 = uncompressed, 1 = fixed huffman tree, 2 = dynamic huffman tree (best compression) +*) useLZ77: whether or not to use LZ77 for compressed block types +*) windowSize: the window size used by the LZ77 encoder (1 - 32768) +*) force_palette: if colorType is 2 or 6, you can make the encoder write a PLTE + chunk if force_palette is true. This can used as suggested palette to convert + to by viewers that don't support more than 256 colors (if those still exist) +*) add_id: add text chunk "Encoder: LodePNG " to the image. +*) text_compression: default 0. If 1, it'll store texts as zTXt instead of tEXt chunks. + zTXt chunks use zlib compression on the text. This gives a smaller result on + large texts but a larger result on small texts (such as a single program name). + It's all tEXt or all zTXt though, there's no separate setting per text yet. + + +6. color conversions +-------------------- + +In LodePNG, the color mode (bits, channels and palette) used in the PNG image, +and the color mode used in the raw data, are separate and independently +configurable. Therefore, LodePNG needs to do conversions from one color mode to +another. Not all possible conversions are supported (e.g. converting to a palette +or a lower bit depth isn't supported). This section will explain which conversions +are supported and how to configure this. This explains for example when LodePNG +uses the settings in LodePNG_InfoPng, LodePNG_InfoRaw and Settings. + +6.1. PNG color types +-------------------- + +A PNG image can have many color types, ranging from 1-bit color to 64-bit color, +as well as palettized color modes. After the zlib decompression and unfiltering +in the PNG image is done, the raw pixel data will have that color type and thus +a certain amount of bits per pixel. If you want the output raw image after +decoding to have another color type, a conversion is done by LodePNG. + +The PNG specification mentions the following color types: + +0: greyscale, bit depths 1, 2, 4, 8, 16 +2: RGB, bit depths 8 and 16 +3: palette, bit depths 1, 2, 4 and 8 +4: greyscale with alpha, bit depths 8 and 16 +6: RGBA, bit depths 8 and 16 + +Bit depth is the amount of bits per pixel per color channel. So the total amount +of bits per pixel = amount of channels * bitDepth. + +6.2. Default Behaviour of LodePNG +--------------------------------- + +By default, the Decoder will convert the data from the PNG to 32-bit RGBA color, +no matter what color type the PNG has, so that the result can be used directly +as a texture in OpenGL etc... without worries about what color type the original +image has. + +The Encoder assumes by default that the raw input you give it is a 32-bit RGBA +buffer and will store the PNG as either 32 bit or 24 bit depending on whether +or not any translucent pixels were detected in it. + +To get the default behaviour, don't change the values of LodePNG_InfoRaw and +LodePNG_InfoPng of the encoder, and don't change the values of LodePNG_InfoRaw +of the decoder. + +6.3. Color Conversions +---------------------- + +As explained in the sections about the Encoder and Decoder, you can specify +color types and bit depths in LodePNG_InfoPng and LodePNG_InfoRaw, to change the default behaviour +explained above. (for the Decoder you can only specify the LodePNG_InfoRaw, because the +LodePNG_InfoPng contains what the PNG file has). + +To avoid some confusion: +-the Decoder converts from PNG to raw image +-the Encoder converts from raw image to PNG +-the color type and bit depth in LodePNG_InfoRaw, are those of the raw image +-the color type and bit depth in LodePNG_InfoPng, are those of the PNG +-if the color type of the LodePNG_InfoRaw and PNG image aren't the same, a conversion +between the color types is done if the color types are supported. If it is not +supported, an error is returned. If the types are the same, no conversion is done +and this is supported for any color type. + +Supported color conversions: +-It's possible to load PNGs from any colortype and to save PNGs of any colorType. +-Both encoder and decoder use the same converter. So both encoder and decoder +suport the same color types at the input and the output. So the decoder supports +any type of PNG image and can convert it to certain types of raw image, while the +encoder supports any type of raw data but only certain color types for the output PNG. +-The converter can convert from _any_ input color type, to 24-bit RGB or 32-bit RGBA (8 bits per channel) +-The converter can convert from _any_ input color type, to 48-bit RGB or 64-bit RGBA (16 bits per channel) +-The converter can convert from greyscale input color type, to 8-bit greyscale or greyscale with alpha +-The converter can convert from greyscale input color type, to 16-bit greyscale or greyscale with alpha +-If both color types are the same, conversion from anything to anything is possible +-Color types that are invalid according to the PNG specification are not allowed +-When converting from a type with alpha channel to one without, the alpha channel information is discarded +-When converting from a type without alpha channel to one with, the result will be opaque except + pixels that have the same color as the color key of the input if one was given +-When converting from 16-bit bitDepth to 8-bit bitDepth, the 16-bit precision information is lost, + only the most significant byte is kept +-When converting from 8-bit bitDepth to 16-bit bitDepth, the 8-bit byte is duplicated, so that 0 stays 0, and + the 8-bit maximum 255 is converted to the 16-bit maximum 65535. Something similar happens at bit level for + converting very low bit depths to 8 or 16 bit. +-Converting from color to greyscale or to palette is not supported on purpose: there are multiple possible + algorithms to do this color reduction, LodePNG does not want to pick one and leaves this choice to the + user instead, because it's beyond the scope of PNG encoding. +-Converting from a palette to a palette, only keeps the indices, it ignores the colors defined in the + palette without giving an error + +No conversion needed...: +-If the color type of the PNG image and raw image are the same, then no +conversion is done, and all color types are supported. +-In the encoder, you can make it save a PNG with any color by giving the +LodePNG_InfoRaw and LodePNG_InfoPng the same color type. +-In the decoder, you can make it store the pixel data in the same color type +as the PNG has, by setting the color_convert setting to false. Settings in +infoRaw are then ignored. + +The function LodePNG_convert does this, which is available in the interface but +normally isn't needed since the encoder and decoder already call it. + +6.4. More Notes +--------------- + +In the PNG file format, if a less than 8-bit per pixel color type is used and the scanlines +have a bit amount that isn't a multiple of 8, then padding bits are used so that each +scanline starts at a fresh byte. But that is NOT true for the LodePNG input and output! +The raw input image you give to the encoder, and the raw output image you get from the decoder +will NOT have these padding bits, e.g. in the case of a 1-bit image with a width +of 7 pixels, the first pixel of the second scanline will the the 8th bit of the first byte, +not the first bit of a new byte. + +7. error values +--------------- + +All functions in LodePNG that return an error code, return 0 if everything went +OK, or one of the code defined by LodePNG if there was an error. + +The meaning of the LodePNG error values can be retrieved with the function +LodePNG_error_text: given the numerical error code, it returns a description +of the error in English as a string. + +Check the implementation of LodePNG_error_text to see the meaning of each error code. + + +8. chunks and PNG editing +------------------------- + +If you want to add extra chunks to a PNG you encode, or use LodePNG for a PNG +editor that should follow the rules about handling of unknown chunks, or if you +program is able to read other types of chunks than the ones handled by LodePNG, +then that's possible with the chunk functions of LodePNG. + +A PNG chunk has the following layout: + +4 bytes length +4 bytes type name +length bytes data +4 bytes CRC + + +8.1. iterating through chunks +----------------------------- + +If you have a buffer containing the PNG image data, then the first chunk (the +IHDR chunk) starts at byte number 8 of that buffer. The first 8 bytes are the +signature of the PNG and are not part of a chunk. But if you start at byte 8 +then you have a chunk, and can check the following things of it. + +NOTE: none of these functions check for memory buffer boundaries. To avoid +exploits, always make sure the buffer contains all the data of the chunks. +When using LodePNG_chunk_next, make sure the returned value is within the +allocated memory. + +unsigned LodePNG_chunk_length(const unsigned char* chunk): + +Get the length of the chunk's data. The total chunk length is this length + 12. + +void LodePNG_chunk_type(char type[5], const unsigned char* chunk): +unsigned char LodePNG_chunk_type_equals(const unsigned char* chunk, const char* type): + +Get the type of the chunk or compare if it's a certain type + +unsigned char LodePNG_chunk_critical(const unsigned char* chunk): +unsigned char LodePNG_chunk_private(const unsigned char* chunk): +unsigned char LodePNG_chunk_safetocopy(const unsigned char* chunk): + +Check if the chunk is critical in the PNG standard (only IHDR, PLTE, IDAT and IEND are). +Check if the chunk is private (public chunks are part of the standard, private ones not). +Check if the chunk is safe to copy. If it's not, then, when modifying data in a critical +chunk, unsafe to copy chunks of the old image may NOT be saved in the new one if your +program doesn't handle that type of unknown chunk. + +unsigned char* LodePNG_chunk_data(unsigned char* chunk): +const unsigned char* LodePNG_chunk_data_const(const unsigned char* chunk): + +Get a pointer to the start of the data of the chunk. + +unsigned LodePNG_chunk_check_crc(const unsigned char* chunk): +void LodePNG_chunk_generate_crc(unsigned char* chunk): + +Check if the crc is correct or generate a correct one. + +unsigned char* LodePNG_chunk_next(unsigned char* chunk): +const unsigned char* LodePNG_chunk_next_const(const unsigned char* chunk): + +Iterate to the next chunk. This works if you have a buffer with consecutive chunks. Note that these +functions do no boundary checking of the allocated data whatsoever, so make sure there is enough +data available in the buffer to be able to go to the next chunk. + +unsigned LodePNG_append_chunk(unsigned char** out, size_t* outlength, const unsigned char* chunk): +unsigned LodePNG_create_chunk(unsigned char** out, size_t* outlength, unsigned length, + const char* type, const unsigned char* data): + +These functions are used to create new chunks that are appended to the data in *out that has +length *outlength. The append function appends an existing chunk to the new data. The create +function creates a new chunk with the given parameters and appends it. Type is the 4-letter +name of the chunk. + + +8.2. chunks in infoPng +---------------------- + +The LodePNG_InfoPng struct contains a struct LodePNG_UnknownChunks in it. This +struct has 3 buffers (each with size) to contain 3 types of unknown chunks: +the ones that come before the PLTE chunk, the ones that come between the PLTE +and the IDAT chunks, and the ones that come after the IDAT chunks. +It's necessary to make the distionction between these 3 cases because the PNG +standard forces to keep the ordering of unknown chunks compared to the critical +chunks, but does not force any other ordering rules. + +infoPng.unknown_chunks.data[0] is the chunks before PLTE +infoPng.unknown_chunks.data[1] is the chunks after PLTE, before IDAT +infoPng.unknown_chunks.data[2] is the chunks after IDAT + +The chunks in these 3 buffers can be iterated through and read by using the same +way described in the previous subchapter. + +When using the decoder to decode a PNG, you can make it store all unknown chunks +if you set the option settings.rememberUnknownChunks to 1. By default, this option +is off and is 0. + +The encoder will always encode unknown chunks that are stored in the infoPng. If +you need it to add a particular chunk that isn't known by LodePNG, you can use +LodePNG_append_chunk or LodePNG_create_chunk to the chunk data in +infoPng.unknown_chunks.data[x]. + +Chunks that are known by LodePNG should not be added in that way. E.g. to make +LodePNG add a bKGD chunk, set background_defined to true and add the correct +parameters there and LodePNG will generate the chunk. + + +9. compiler support +------------------- + +No libraries other than the current standard C library are needed to compile +LodePNG. For the C++ version, only the standard C++ library is needed on top. +Add the files lodepng.c(pp) and lodepng.h to your project, include +lodepng.h where needed, and your program can read/write PNG files. + +Use optimization! For both the encoder and decoder, compiling with the best +optimizations makes a large difference. + +Make sure that LodePNG is compiled with the same compiler of the same version +and with the same settings as the rest of the program, or the interfaces with +std::vectors and std::strings in C++ can be incompatible resulting in bad things. + +CHAR_BITS must be 8 or higher, because LodePNG uses unsigned chars for octets. + +*) gcc and g++ + +LodePNG is developed in gcc so this compiler is natively supported. It gives no +warnings with compiler options "-Wall -Wextra -pedantic -ansi", with gcc and g++ +version 4.5.1 on Linux. + +*) Mingw and Bloodshed DevC++ + +The Mingw compiler (a port of gcc) used by Bloodshed DevC++ for Windows is fully +supported by LodePNG. + +*) Visual Studio 2005 and Visual C++ 2005 Express Edition + +Versions 20070604 up to 20080107 have been tested on VS2005 and work. Visual +studio may give warnings about 'fopen' being deprecated. A multiplatform library +can't support the proposed Visual Studio alternative however. + +If you're using LodePNG in VS2005 and don't want to see the deprecated warnings, +put this on top of lodepng.h before the inclusions: #define _CRT_SECURE_NO_DEPRECATE + +*) Visual Studio 6.0 + +The C++ version of LodePNG was not supported by Visual Studio 6.0 because Visual +Studio 6.0 doesn't follow the C++ standard and implements it incorrectly. +The current C version of LodePNG has not been tested in VS6 but may work now. + +*) Comeau C/C++ + +Vesion 20070107 compiles without problems on the Comeau C/C++ Online Test Drive +at http://www.comeaucomputing.com/tryitout in both C90 and C++ mode. + +*) Compilers on Macintosh + +LodePNG has been reported to work both with the gcc and LLVM for Macintosh, both +for C and C++. + +*) Other Compilers + +If you encounter problems on other compilers, I'm happy to help out make LodePNG +support the compiler if it supports the ISO C90 and C++ standard well enough and +the required modification doesn't require using non standard or less good C/C++ +code or headers. + + +10. examples +------------ + +This decoder and encoder example show the most basic usage of LodePNG (using the +classes, not the simple functions, which would be trivial) + +More complex examples can be found in: +-lodepng_examples.c: 9 different examples in C, such as showing the image with SDL, ... +-lodepng_examples.cpp: the same examples in C++ using the C++ wrapper of LodePNG + +These files can be found on the LodePNG website or searched for on the internet. + +10.1. decoder C++ example +------------------------- + +//////////////////////////////////////////////////////////////////////////////// +#include "lodepng.h" +#include + +int main(int argc, char *argv[]) +{ + const char* filename = argc > 1 ? argv[1] : "test.png"; + + //load and decode + std::vector buffer, image; //buffer will contain the PNG file, image will contain the raw pixels + LodePNG::loadFile(buffer, filename); //load the image file with given filename + LodePNG::Decoder decoder; + decoder.decode(image, buffer.size() ? &buffer[0] : 0, (unsigned)buffer.size()); //decode the png + + //if there's an error, display it + if(decoder.hasError()) + std::cout << "error " << decoder.getError() << ": "<< LodePNG_error_text(decoder.getError()) << std::endl; + + int width = decoder.getWidth(); //get the width in pixels + int height = decoder.getHeight(); //get the height in pixels + //the pixels are now in the vector "image", 4 bytes per pixel, ordered RGBARGBA..., use it as texture, draw it, ... +} + +//alternative version using the "simple" function +int main(int argc, char *argv[]) +{ + const char* filename = argc > 1 ? argv[1] : "test.png"; + + //load and decode + std::vector image; + unsigned width, height; + unsigned error = LodePNG::decode(image, width, height, filename); + + //if there's an error, display it + if(error != 0) std::cout << "error " << error << ": " << LodePNG_error_text(error) << std::endl; + + //the pixels are now in the vector "image", 4 bytes per pixel, ordered RGBARGBA..., use it as texture, draw it, ... +} +//////////////////////////////////////////////////////////////////////////////// + + +10.2. encoder C++ example +------------------------- + +//////////////////////////////////////////////////////////////////////////////// +#include "lodepng.h" +#include + +//saves image to filename given as argument. Warning, this overwrites the file without warning! +int main(int argc, char *argv[]) +{ + //check if user gave a filename + if(argc <= 1) + { + std::cout << "please provide a filename to save to\n"; + return 0; + } + + //generate some image + std::vector image; + image.resize(512 * 512 * 4); + for(unsigned y = 0; y < 512; y++) + for(unsigned x = 0; x < 512; x++) + { + image[4 * 512 * y + 4 * x + 0] = 255 * !(x & y); + image[4 * 512 * y + 4 * x + 1] = x ^ y; + image[4 * 512 * y + 4 * x + 2] = x | y; + image[4 * 512 * y + 4 * x + 3] = 255; + } + + //encode and save, using the Encoder class + std::vector buffer; + LodePNG::Encoder encoder; + encoder.encode(buffer, image, 512, 512); + LodePNG::saveFile(buffer, argv[1]); + + //the same as the 4 lines of code above, but in 1 call without the class: + //LodePNG::encode(argv[1], image, 512, 512); +} +//////////////////////////////////////////////////////////////////////////////// + + +10.3. Decoder C example +----------------------- + +This example loads the PNG from a file into a pixel buffer in 1 function call + +#include "lodepng.h" + +int main(int argc, char *argv[]) +{ + unsigned error; + unsigned char* image; + size_t width, height; + + if(argc <= 1) return 0; + + error = LodePNG_decode32_file(&image, &width, &height, filename); + + if(error != 0) printf("error %u: %s\n", error, LodePNG_error_text(error)); + + //use image here + + free(image); +} + + +11. changes +----------- + +The version number of LodePNG is the date of the change given in the format +yyyymmdd. + +Some changes aren't backwards compatible. Those are indicated with a (!) +symbol. + +*) 8 sep 2011: lz77 encoder lazy matching instead of greedy matching. +*) 23 aug 2011: tweaked the zlib compression parameters after benchmarking. + A bug with the PNG filtertype heuristic was fixed, so that it chooses much + better ones (it's quite significant). A setting to do an experimental, slow, + brute force search for PNG filter types is added. +*) 17 aug 2011 (!): changed some C zlib related function names. +*) 16 aug 2011: made the code less wide (max 120 characters per line). +*) 17 apr 2011: code cleanup. Bugfixes. Convert low to 16-bit per sample colors. +*) 21 feb 2011: fixed compiling for C90. Fixed compiling with sections disabled. +*) 11 dec 2010: encoding is made faster, based on suggestion by Peter Eastman + to optimize long sequences of zeros. +*) 13 nov 2010: added LodePNG_InfoColor_hasPaletteAlpha and + LodePNG_InfoColor_canHaveAlpha functions for convenience. +*) 7 nov 2010: added LodePNG_error_text function to get error code description. +*) 30 okt 2010: made decoding slightly faster +*) 26 okt 2010: (!) changed some C function and struct names (more consistent). + Reorganized the documentation and the declaration order in the header. +*) 08 aug 2010: only changed some comments and external samples. +*) 05 jul 2010: fixed bug thanks to warnings in the new gcc version. +*) 14 mar 2010: fixed bug where too much memory was allocated for char buffers. +*) 02 sep 2008: fixed bug where it could create empty tree that linux apps could + read by ignoring the problem but windows apps couldn't. +*) 06 jun 2008: added more error checks for out of memory cases. +*) 26 apr 2008: added a few more checks here and there to ensure more safety. +*) 06 mar 2008: crash with encoding of strings fixed +*) 02 feb 2008: support for international text chunks added (iTXt) +*) 23 jan 2008: small cleanups, and #defines to divide code in sections +*) 20 jan 2008: support for unknown chunks allowing using LodePNG for an editor. +*) 18 jan 2008: support for tIME and pHYs chunks added to encoder and decoder. +*) 17 jan 2008: ability to encode and decode compressed zTXt chunks added + Also vareous fixes, such as in the deflate and the padding bits code. +*) 13 jan 2008: Added ability to encode Adam7-interlaced images. Improved + filtering code of encoder. +*) 07 jan 2008: (!) changed LodePNG to use ISO C90 instead of C++. A + C++ wrapper around this provides an interface almost identical to before. + Having LodePNG be pure ISO C90 makes it more portable. The C and C++ code + are together in these files but it works both for C and C++ compilers. +*) 29 dec 2007: (!) changed most integer types to unsigned int + other tweaks +*) 30 aug 2007: bug fixed which makes this Borland C++ compatible +*) 09 aug 2007: some VS2005 warnings removed again +*) 21 jul 2007: deflate code placed in new namespace separate from zlib code +*) 08 jun 2007: fixed bug with 2- and 4-bit color, and small interlaced images +*) 04 jun 2007: improved support for Visual Studio 2005: crash with accessing + invalid std::vector element [0] fixed, and level 3 and 4 warnings removed +*) 02 jun 2007: made the encoder add a tag with version by default +*) 27 may 2007: zlib and png code separated (but still in the same file), + simple encoder/decoder functions added for more simple usage cases +*) 19 may 2007: minor fixes, some code cleaning, new error added (error 69), + moved some examples from here to lodepng_examples.cpp +*) 12 may 2007: palette decoding bug fixed +*) 24 apr 2007: changed the license from BSD to the zlib license +*) 11 mar 2007: very simple addition: ability to encode bKGD chunks. +*) 04 mar 2007: (!) tEXt chunk related fixes, and support for encoding + palettized PNG images. Plus little interface change with palette and texts. +*) 03 mar 2007: Made it encode dynamic Huffman shorter with repeat codes. + Fixed a bug where the end code of a block had length 0 in the Huffman tree. +*) 26 feb 2007: Huffman compression with dynamic trees (BTYPE 2) now implemented + and supported by the encoder, resulting in smaller PNGs at the output. +*) 27 jan 2007: Made the Adler-32 test faster so that a timewaste is gone. +*) 24 jan 2007: gave encoder an error interface. Added color conversion from any + greyscale type to 8-bit greyscale with or without alpha. +*) 21 jan 2007: (!) Totally changed the interface. It allows more color types + to convert to and is more uniform. See the manual for how it works now. +*) 07 jan 2007: Some cleanup & fixes, and a few changes over the last days: + encode/decode custom tEXt chunks, separate classes for zlib & deflate, and + at last made the decoder give errors for incorrect Adler32 or Crc. +*) 01 jan 2007: Fixed bug with encoding PNGs with less than 8 bits per channel. +*) 29 dec 2006: Added support for encoding images without alpha channel, and + cleaned out code as well as making certain parts faster. +*) 28 dec 2006: Added "Settings" to the encoder. +*) 26 dec 2006: The encoder now does LZ77 encoding and produces much smaller files now. + Removed some code duplication in the decoder. Fixed little bug in an example. +*) 09 dec 2006: (!) Placed output parameters of public functions as first parameter. + Fixed a bug of the decoder with 16-bit per color. +*) 15 okt 2006: Changed documentation structure +*) 09 okt 2006: Encoder class added. It encodes a valid PNG image from the + given image buffer, however for now it's not compressed. +*) 08 sep 2006: (!) Changed to interface with a Decoder class +*) 30 jul 2006: (!) LodePNG_InfoPng , width and height are now retrieved in different + way. Renamed decodePNG to decodePNGGeneric. +*) 29 jul 2006: (!) Changed the interface: image info is now returned as a + struct of type LodePNG::LodePNG_Info, instead of a vector, which was a bit clumsy. +*) 28 jul 2006: Cleaned the code and added new error checks. + Corrected terminology "deflate" into "inflate". +*) 23 jun 2006: Added SDL example in the documentation in the header, this + example allows easy debugging by displaying the PNG and its transparency. +*) 22 jun 2006: (!) Changed way to obtain error value. Added + loadFile function for convenience. Made decodePNG32 faster. +*) 21 jun 2006: (!) Changed type of info vector to unsigned. + Changed position of palette in info vector. Fixed an important bug that + happened on PNGs with an uncompressed block. +*) 16 jun 2006: Internally changed unsigned into unsigned where + needed, and performed some optimizations. +*) 07 jun 2006: (!) Renamed functions to decodePNG and placed them + in LodePNG namespace. Changed the order of the parameters. Rewrote the + documentation in the header. Renamed files to lodepng.cpp and lodepng.h +*) 22 apr 2006: Optimized and improved some code +*) 07 sep 2005: (!) Changed to std::vector interface +*) 12 aug 2005: Initial release (C++, decoder only) + + +12. contact information +----------------------- + +Feel free to contact me with suggestions, problems, comments, ... concerning +LodePNG. If you encounter a PNG image that doesn't work properly with this +decoder, feel free to send it and I'll use it to find and fix the problem. + +My email address is (puzzle the account and domain together with an @ symbol): +Domain: gmail dot com. +Account: lode dot vandevenne. + + +Copyright (c) 2005-2011 Lode Vandevenne +*/ diff --git a/tests/test_cmdline_tool.py b/tests/test_cmdline_tool.py index b8a0d071..7f687755 100755 --- a/tests/test_cmdline_tool.py +++ b/tests/test_cmdline_tool.py @@ -43,8 +43,15 @@ def verify_test(testname, cmd): return True def execute_and_redirect(cmd, params, outfile): - proc = subprocess.Popen([cmd] + params, stdout=outfile) - retval = proc.wait() + retval = -1 + try: + proc = subprocess.Popen([cmd] + params, stdout=outfile) + retval = proc.wait() + except OSError as (errno, strerror): + print >> sys.stderr, "Error: ", errno, strerror + print >> sys.stderr, " cmd:", cmd + print >> sys.stderr, " params:", params + print >> sys.stderr, " outfile:", outfile return retval def get_normalized_text(filename): @@ -61,7 +68,7 @@ def compare_default(resultfilename): return True def compare_png(resultfilename): - if execute_and_redirect("diff", [expectedfilename, resultfilename], sys.stderr) != 0: + if execute_and_redirect("./yee_compare", [expectedfilename, resultfilename], sys.stderr) != 0: return False return True diff --git a/tests/yee_compare.cpp b/tests/yee_compare.cpp new file mode 100644 index 00000000..e135b9cf --- /dev/null +++ b/tests/yee_compare.cpp @@ -0,0 +1,680 @@ + +#include "yee_compare.h" +#include "lodepng.h" +#include +#include +#include +#include + +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=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 new file mode 100644 index 00000000..dc616f7d --- /dev/null +++ b/tests/yee_compare.h @@ -0,0 +1,125 @@ +/* +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 +*/ + +#ifndef _yee_compare_h +#define _yee_compare_h + +#include + +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 + From 0b219ae4b46bbab5593d0ee644d7f6bcdb36d17e Mon Sep 17 00:00:00 2001 From: Don Bright Date: Sun, 18 Sep 2011 17:53:33 -0500 Subject: [PATCH 05/93] opencsg testing --- doc/testing.txt | 15 +++++++++++++++ src/CGALRenderer.cc | 8 +++++--- src/dxfdata.h | 7 ++++++- src/polyset.h | 7 ++++++- tests/OffscreenContext.cc | 2 +- tests/OffscreenView.h | 3 +++ tests/cgalpngtest.cc | 1 + tests/opencsgtest.cc | 18 ++++++++++-------- tests/test_cmdline_tool.py | 8 ++++++-- 9 files changed, 53 insertions(+), 16 deletions(-) diff --git a/doc/testing.txt b/doc/testing.txt index b64d5a46..26000c6a 100644 --- a/doc/testing.txt +++ b/doc/testing.txt @@ -27,3 +27,18 @@ Adding a new regression test: Note that test files which don't have an *-expected. file will be ignored for the test apps in question. + +Troubleshooting a failed test: +------------------------------ + +You can run a single test by running + $ ctest -R testname + +You can run a series by running + $ ctest -R cgalpng # runs all cgalpng tests + $ ctest -R sphere # runs all sphere tests + +Logs of test runs are found in tests/build/Testing/Temporary +Expected results are found in tests/regression/* +Actual results are found in tests/build/testname-output/* + diff --git a/src/CGALRenderer.cc b/src/CGALRenderer.cc index 4d165ce4..95bcba15 100644 --- a/src/CGALRenderer.cc +++ b/src/CGALRenderer.cc @@ -24,10 +24,12 @@ * */ -#include "CGALRenderer.h" -#include "polyset.h" -#include "CGAL_renderer.h" +// dxfdata.h must come first for Eigen SIMD alignment issues #include "dxfdata.h" +#include "polyset.h" + +#include "CGALRenderer.h" +#include "CGAL_renderer.h" #include "dxftess.h" #include "CGAL_Nef_polyhedron.h" #include "cgal.h" diff --git a/src/dxfdata.h b/src/dxfdata.h index bada0312..4e4b4ab6 100644 --- a/src/dxfdata.h +++ b/src/dxfdata.h @@ -1,8 +1,13 @@ #ifndef DXFDATA_H_ #define DXFDATA_H_ -#include +#ifndef __APPLE__ +#define EIGEN_DONT_VECTORIZE 1 +#define EIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT 1 +#endif + #include +#include using Eigen::Vector2d; diff --git a/src/polyset.h b/src/polyset.h index c59d86a8..3cfb21ab 100644 --- a/src/polyset.h +++ b/src/polyset.h @@ -1,11 +1,16 @@ #ifndef POLYSET_H_ #define POLYSET_H_ +#ifndef __APPLE__ +#define EIGEN_DONT_VECTORIZE 1 +#define EIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT 1 +#endif + #include #include "grid.h" -#include #include #include +#include using Eigen::Vector3d; typedef Eigen::AlignedBox BoundingBox; diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc index d5c4c097..66b4ab30 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContext.cc @@ -181,7 +181,7 @@ bool save_framebuffer(OffscreenContext *ctx, const char *filename) //char * filename2="blah2.tga"; //PRINTF("writing %s\n",filename2); //write_targa(filename2,pixels,ctx->width, ctx->height); - char * filename2="blah2.png"; + //char * filename2="blah2.png"; PRINTF("writing %s . . .",filename); //write_targa(filename2,pixels,ctx->width, ctx->height); write_png(filename,pixels,ctx->width, ctx->height); diff --git a/tests/OffscreenView.h b/tests/OffscreenView.h index 587255ac..85c01446 100644 --- a/tests/OffscreenView.h +++ b/tests/OffscreenView.h @@ -1,6 +1,9 @@ #ifndef OFFSCREENVIEW_H_ #define OFFSCREENVIEW_H_ +#define EIGEN_DONT_VECTORIZE 1 +#define EIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT 1 + #include "OffscreenContext.h" #include #include diff --git a/tests/cgalpngtest.cc b/tests/cgalpngtest.cc index d7c6886c..cae5c670 100644 --- a/tests/cgalpngtest.cc +++ b/tests/cgalpngtest.cc @@ -178,6 +178,7 @@ int main(int argc, char **argv) QDir::setCurrent(original_path.absolutePath()); +// match with csgtest ends csgInfo.glview = new OffscreenView(512,512); //glewInit(); diff --git a/tests/opencsgtest.cc b/tests/opencsgtest.cc index 98e10370..d5e83ddc 100644 --- a/tests/opencsgtest.cc +++ b/tests/opencsgtest.cc @@ -1,4 +1,4 @@ -#include +//#include #include "openscad.h" #include "handle_dep.h" #include "builtin.h" @@ -144,9 +144,9 @@ int main(int argc, char *argv[]) CsgInfo csgInfo; CGALEvaluator cgalevaluator(tree); CSGTermEvaluator evaluator(tree, &cgalevaluator.psevaluator); - CSGTerm *root_raw_term = evaluator.evaluateCSGTerm(*root_node, - csgInfo.highlight_terms, - csgInfo.background_terms); + CSGTerm *root_raw_term = evaluator.evaluateCSGTerm(*root_node, + csgInfo.highlight_terms, + csgInfo.background_terms); if (!root_raw_term) { cerr << "Error: CSG generation failed! (no top level object found)\n"; @@ -203,6 +203,8 @@ int main(int argc, char *argv[]) QDir::setCurrent(original_path.absolutePath()); + + csgInfo.glview = new OffscreenView(512,512); BoundingBox bbox = csgInfo.root_chain->getBoundingBox(); @@ -214,7 +216,7 @@ int main(int argc, char *argv[]) Vector3d camerapos = center - radius*1.8*cameradir; csgInfo.glview->setCamera(camerapos, center); - glewInit(); + //glewInit(); #ifdef DEBUG cout << "GLEW version " << glewGetString(GLEW_VERSION) << "\n"; cout << (const char *)glGetString(GL_RENDERER) << "(" << (const char *)glGetString(GL_VENDOR) << ")\n" @@ -234,9 +236,9 @@ int main(int argc, char *argv[]) #endif OpenCSGRenderer opencsgRenderer(csgInfo.root_chain, csgInfo.highlights_chain, csgInfo.background_chain, csgInfo.glview->shaderinfo); - ThrownTogetherRenderer thrownTogetherRenderer(csgInfo.root_chain, csgInfo.highlights_chain, csgInfo.background_chain); - csgInfo.glview->setRenderer(&thrownTogetherRenderer); -// csgInfo.glview->setRenderer(&opencsgRenderer); + //ThrownTogetherRenderer thrownTogetherRenderer(csgInfo.root_chain, csgInfo.highlights_chain, csgInfo.background_chain); + //csgInfo.glview->setRenderer(&thrownTogetherRenderer); + csgInfo.glview->setRenderer(&opencsgRenderer); csgInfo.glview->paintGL(); diff --git a/tests/test_cmdline_tool.py b/tests/test_cmdline_tool.py index 7f687755..07afcc87 100755 --- a/tests/test_cmdline_tool.py +++ b/tests/test_cmdline_tool.py @@ -47,8 +47,8 @@ def execute_and_redirect(cmd, params, outfile): try: proc = subprocess.Popen([cmd] + params, stdout=outfile) retval = proc.wait() - except OSError as (errno, strerror): - print >> sys.stderr, "Error: ", errno, strerror + except: + print >> sys.stderr, "Error running subprocess: ", sys.exc_info()[1] print >> sys.stderr, " cmd:", cmd print >> sys.stderr, " params:", params print >> sys.stderr, " outfile:", outfile @@ -68,6 +68,10 @@ def compare_default(resultfilename): return True def compare_png(resultfilename): + if not resultfilename: + print >> sys.stderr, "Error: OpenSCAD did not generate an image" + return False + print >> sys.stderr, 'Yee image compare: ', expectedfilename, ' ', resultfilename if execute_and_redirect("./yee_compare", [expectedfilename, resultfilename], sys.stderr) != 0: return False return True From 24fa66ca38a5f9ce656af4d72109d9ad3a1e2526 Mon Sep 17 00:00:00 2001 From: Don Bright Date: Sun, 18 Sep 2011 18:21:52 -0500 Subject: [PATCH 06/93] copyright notice --- tests/opencsgtest.cc | 8 ++++---- tests/yee_compare.cpp | 1 + tests/yee_compare.h | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/opencsgtest.cc b/tests/opencsgtest.cc index d5e83ddc..ff5d2961 100644 --- a/tests/opencsgtest.cc +++ b/tests/opencsgtest.cc @@ -235,10 +235,10 @@ int main(int argc, char *argv[]) } #endif - OpenCSGRenderer opencsgRenderer(csgInfo.root_chain, csgInfo.highlights_chain, csgInfo.background_chain, csgInfo.glview->shaderinfo); - //ThrownTogetherRenderer thrownTogetherRenderer(csgInfo.root_chain, csgInfo.highlights_chain, csgInfo.background_chain); - //csgInfo.glview->setRenderer(&thrownTogetherRenderer); - csgInfo.glview->setRenderer(&opencsgRenderer); + //OpenCSGRenderer opencsgRenderer(csgInfo.root_chain, csgInfo.highlights_chain, csgInfo.background_chain, csgInfo.glview->shaderinfo); + //csgInfo.glview->setRenderer(&opencsgRenderer); + ThrownTogetherRenderer thrownTogetherRenderer(csgInfo.root_chain, csgInfo.highlights_chain, csgInfo.background_chain); + csgInfo.glview->setRenderer(&thrownTogetherRenderer); csgInfo.glview->paintGL(); diff --git a/tests/yee_compare.cpp b/tests/yee_compare.cpp index e135b9cf..9de47202 100644 --- a/tests/yee_compare.cpp +++ b/tests/yee_compare.cpp @@ -1,3 +1,4 @@ +// modified from PerceptualDiff source for OpenSCAD, 2011 September #include "yee_compare.h" #include "lodepng.h" diff --git a/tests/yee_compare.h b/tests/yee_compare.h index dc616f7d..17b2e6fa 100644 --- a/tests/yee_compare.h +++ b/tests/yee_compare.h @@ -1,3 +1,4 @@ +// source code modified for OpenSCAD, Sept 2011 /* Metric RGBAImage.h From ccc88b0c433e9d66ea24dfd26ad6e835196acca5 Mon Sep 17 00:00:00 2001 From: Don Bright Date: Mon, 19 Sep 2011 21:25:05 -0500 Subject: [PATCH 07/93] get throwntogetherrenderer working in tests (initalize csgInfo to null) --- src/csgterm.cc | 20 ++++++++++++++ src/csgterm.h | 1 + src/mainwin.cc | 13 +++++++++ tests/OffscreenView.h | 2 ++ tests/opencsgtest.cc | 62 +++++++++++++++++++++---------------------- 5 files changed, 66 insertions(+), 32 deletions(-) diff --git a/src/csgterm.cc b/src/csgterm.cc index 8306aaf5..5bccff8f 100644 --- a/src/csgterm.cc +++ b/src/csgterm.cc @@ -227,6 +227,26 @@ std::string CSGChain::dump() return dump.str(); } + +std::string CSGChain::fulldump() +{ + std::stringstream dump; + dump << "\nsizes: \n"; + dump << " polysets: " << polysets.size(); + dump << " matrices: " << matrices.size(); + dump << " colors: " << colors.size(); + dump << " types: " << types.size(); + dump << " labels: " << labels.size(); + dump << "\ndata: \n"; + for (size_t i = 0; i < polysets.size(); i++) dump << (*polysets[i]).polygons.size() << "\n"; + for (size_t i = 0; i < matrices.size(); i++) dump << *matrices[i] << "\n"; + for (size_t i = 0; i < colors.size(); i++) dump << *colors[i] << "\n"; + for (size_t i = 0; i < types.size(); i++) dump << types[i] << "\n"; + for (size_t i = 0; i < labels.size(); i++) dump << labels[i] << "\n"; + dump << "\n"; + return dump.str(); +} + BoundingBox CSGChain::getBoundingBox() const { BoundingBox bbox; diff --git a/src/csgterm.h b/src/csgterm.h index c12b7aea..c4e88a60 100644 --- a/src/csgterm.h +++ b/src/csgterm.h @@ -50,6 +50,7 @@ public: void add(const shared_ptr &polyset, double *m, double *color, CSGTerm::type_e type, std::string label); void import(CSGTerm *term, CSGTerm::type_e type = CSGTerm::TYPE_UNION); std::string dump(); + std::string fulldump(); BoundingBox getBoundingBox() const; }; diff --git a/src/mainwin.cc b/src/mainwin.cc index 56502f23..f3f53a16 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -95,6 +95,8 @@ using namespace boost::lambda; #endif // ENABLE_CGAL +using std::cerr; + // Global application state unsigned int GuiLocker::gui_locked = 0; @@ -876,6 +878,17 @@ void MainWindow::compileCSG(bool procevents) this->highlights_chain, this->background_chain); + fprintf(stderr, "Dump root chain\n"); + cerr << this->root_chain->fulldump(); + cerr << this->highlights_chain; + cerr << this->background_chain; +/* fprintf(stderr, "dump highlights\n"); + this->highlights_chain->dump(); + fprintf(stderr, "dump background\n"); + this->background_chain->dump();*/ + fprintf(stderr, "end dump\n"); + + PRINT("CSG generation finished."); int s = t.elapsed() / 1000; PRINTF("Total rendering time: %d hours, %d minutes, %d seconds", s / (60*60), (s / 60) % 60, s % 60); diff --git a/tests/OffscreenView.h b/tests/OffscreenView.h index 85c01446..dd52a810 100644 --- a/tests/OffscreenView.h +++ b/tests/OffscreenView.h @@ -1,8 +1,10 @@ #ifndef OFFSCREENVIEW_H_ #define OFFSCREENVIEW_H_ +#ifndef __APPLE__ // Eigen SIMD alignment #define EIGEN_DONT_VECTORIZE 1 #define EIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT 1 +#endif #include "OffscreenContext.h" #include diff --git a/tests/opencsgtest.cc b/tests/opencsgtest.cc index ff5d2961..4043d4b1 100644 --- a/tests/opencsgtest.cc +++ b/tests/opencsgtest.cc @@ -1,4 +1,4 @@ -//#include +///#include #include "openscad.h" #include "handle_dep.h" #include "builtin.h" @@ -32,8 +32,10 @@ QString librarydir; //#define DEBUG -struct CsgInfo +class CsgInfo { +public: + CsgInfo(); CSGTerm *root_norm_term; // Normalized CSG products class CSGChain *root_chain; std::vector highlight_terms; @@ -43,6 +45,17 @@ struct CsgInfo OffscreenView *glview; }; +CsgInfo::CsgInfo() +{ + this->root_norm_term = NULL; + this->root_chain = NULL;; + this->highlight_terms = vector(); + this->highlights_chain = NULL; + this->background_terms = vector(); + this->background_chain = NULL; + this->glview = NULL; +} + AbstractNode *find_root_tag(AbstractNode *n) { foreach(AbstractNode *v, n->children) { @@ -141,12 +154,11 @@ int main(int argc, char *argv[]) Tree tree(root_node); - CsgInfo csgInfo; + CsgInfo csgInfo = CsgInfo(); + CGALEvaluator cgalevaluator(tree); CSGTermEvaluator evaluator(tree, &cgalevaluator.psevaluator); - CSGTerm *root_raw_term = evaluator.evaluateCSGTerm(*root_node, - csgInfo.highlight_terms, - csgInfo.background_terms); + CSGTerm *root_raw_term = evaluator.evaluateCSGTerm(*root_node, csgInfo.highlight_terms, csgInfo.background_terms); if (!root_raw_term) { cerr << "Error: CSG generation failed! (no top level object found)\n"; @@ -203,45 +215,31 @@ int main(int argc, char *argv[]) QDir::setCurrent(original_path.absolutePath()); + /* + fprintf(stderr, "Dump root chain\n"); + cerr << csgInfo.root_chain->fulldump(); + cerr << csgInfo.highlights_chain; + cerr << csgInfo.background_chain; + fprintf(stderr, "end dump\n"); + */ - + ThrownTogetherRenderer thrownTogetherRenderer(csgInfo.root_chain, csgInfo.highlights_chain, csgInfo.background_chain); csgInfo.glview = new OffscreenView(512,512); - BoundingBox bbox = csgInfo.root_chain->getBoundingBox(); + + + BoundingBox bbox = csgInfo.root_chain->getBoundingBox(); Vector3d center = (bbox.min() + bbox.max()) / 2; double radius = (bbox.max() - bbox.min()).norm() / 2; - - Vector3d cameradir(1, 1, -0.5); Vector3d camerapos = center - radius*1.8*cameradir; csgInfo.glview->setCamera(camerapos, center); - //glewInit(); -#ifdef DEBUG - cout << "GLEW version " << glewGetString(GLEW_VERSION) << "\n"; - cout << (const char *)glGetString(GL_RENDERER) << "(" << (const char *)glGetString(GL_VENDOR) << ")\n" - << "OpenGL version " << (const char *)glGetString(GL_VERSION) << "\n"; - cout << "Extensions: " << (const char *)glGetString(GL_EXTENSIONS) << "\n"; - - - if (GLEW_ARB_framebuffer_object) { - cout << "ARB_FBO supported\n"; - } - if (GLEW_EXT_framebuffer_object) { - cout << "EXT_FBO supported\n"; - } - if (GLEW_EXT_packed_depth_stencil) { - cout << "EXT_packed_depth_stencil\n"; - } -#endif + csgInfo.glview->setRenderer(&thrownTogetherRenderer); //OpenCSGRenderer opencsgRenderer(csgInfo.root_chain, csgInfo.highlights_chain, csgInfo.background_chain, csgInfo.glview->shaderinfo); //csgInfo.glview->setRenderer(&opencsgRenderer); - ThrownTogetherRenderer thrownTogetherRenderer(csgInfo.root_chain, csgInfo.highlights_chain, csgInfo.background_chain); - csgInfo.glview->setRenderer(&thrownTogetherRenderer); - csgInfo.glview->paintGL(); - csgInfo.glview->save("/dev/stdout"); destroy_builtin_functions(); From a2e093c2f1a0d91072c8d5f85567a4a9f565f230 Mon Sep 17 00:00:00 2001 From: Don Bright Date: Wed, 21 Sep 2011 20:11:01 -0500 Subject: [PATCH 08/93] separate throwntogethertest and opencsgtest create 'expected' images for throwntogethertest create imgdiff_fail html file generation --- tests/CMakeLists.txt | 24 +++- tests/opencsgtest.cc | 246 +------------------------------------ tests/test_cmdline_tool.py | 57 ++++++++- 3 files changed, 79 insertions(+), 248 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ed492bea..dca608d7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -143,7 +143,7 @@ else() endif() # -# Yangli Hector Yee's comparison aglorithm +# Yangli Hector Yee's PerceptualDiff code # add_executable(yee_compare yee_compare.cpp lodepng.cpp) @@ -199,7 +199,7 @@ target_link_libraries(cgalpngtest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${ # opencsgtest # -add_executable(opencsgtest opencsgtest.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} +add_executable(opencsgtest opencsgtest.cc csgtestcore.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} ../src/OpenCSGRenderer.cc ../src/ThrownTogetherRenderer.cc ../src/CSGTermEvaluator.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc @@ -208,6 +208,20 @@ add_executable(opencsgtest opencsgtest.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURC set_target_properties(opencsgtest PROPERTIES COMPILE_FLAGS "-DENABLE_OPENCSG -DENABLE_CGAL ${CGAL_CXX_FLAGS_INIT}") target_link_libraries(opencsgtest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${QT_LIBRARIES} ${OPENCSG_LIBRARY} ${GLEW_LIBRARY} ${COCOA_LIBRARY} ${OPENGL_LIBRARY}) +# +# throwntogethertest +# + +add_executable(throwntogethertest throwntogethertest.cc csgtestcore.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} + ../src/OpenCSGRenderer.cc ../src/ThrownTogetherRenderer.cc + ../src/CSGTermEvaluator.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc + ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc + ../src/CGAL_Nef_polyhedron_DxfData.cc ../src/cgaladv_minkowski2.cc ../src/cgaladv_convexhull2.cc + ${COMMON_SOURCES}) +set_target_properties(throwntogethertest PROPERTIES COMPILE_FLAGS "-DENABLE_OPENCSG -DENABLE_CGAL ${CGAL_CXX_FLAGS_INIT}") +target_link_libraries(throwntogethertest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${QT_LIBRARIES} ${OPENCSG_LIBRARY} ${GLEW_LIBRARY} ${COCOA_LIBRARY} ${OPENGL_LIBRARY}) + + # # This functions adds cmd-line tests given files. # Files are sent as the parameters following TESTSUFFIX @@ -279,5 +293,9 @@ add_cmdline_test(cgalpngtest png ${CGALPNGTEST_FILES}) LIST(APPEND OPENCSGTEST_FILES ${CGALPNGTEST_FILES}) add_cmdline_test(opencsgtest png ${OPENCSGTEST_FILES}) +# Add throwntogether tests to CTest +LIST(APPEND THROWNTOGETHERTEST_FILES ${CGALPNGTEST_FILES}) +add_cmdline_test(throwntogethertest png ${THROWNTOGETHERTEST_FILES}) + # Add dxfexport tests to CTest -#add_cmdline_test(${CMAKE_SOURCE_DIR}/../test-code/exportdxf dxf ${SCAD_FILES}) +add_cmdline_test(${CMAKE_SOURCE_DIR}/../test-code/exportdxf dxf ${SCAD_FILES}) diff --git a/tests/opencsgtest.cc b/tests/opencsgtest.cc index 4043d4b1..2d1f5ac2 100644 --- a/tests/opencsgtest.cc +++ b/tests/opencsgtest.cc @@ -1,249 +1,7 @@ -///#include -#include "openscad.h" -#include "handle_dep.h" -#include "builtin.h" -#include "context.h" -#include "node.h" -#include "module.h" -#include "polyset.h" -#include "Tree.h" -#include "CSGTermEvaluator.h" -#include "CGALEvaluator.h" -#include "PolySetCGALEvaluator.h" - -#include "OpenCSGRenderer.h" -#include "ThrownTogetherRenderer.h" - -#include "csgterm.h" -#include "OffscreenView.h" - -#include -#include -#include -#include -#include -#include - -using std::cerr; -using std::cout; - -std::string commandline_commands; -QString librarydir; - -//#define DEBUG - -class CsgInfo -{ -public: - CsgInfo(); - CSGTerm *root_norm_term; // Normalized CSG products - class CSGChain *root_chain; - std::vector highlight_terms; - CSGChain *highlights_chain; - std::vector background_terms; - CSGChain *background_chain; - OffscreenView *glview; -}; - -CsgInfo::CsgInfo() -{ - this->root_norm_term = NULL; - this->root_chain = NULL;; - this->highlight_terms = vector(); - this->highlights_chain = NULL; - this->background_terms = vector(); - this->background_chain = NULL; - this->glview = NULL; -} - -AbstractNode *find_root_tag(AbstractNode *n) -{ - foreach(AbstractNode *v, n->children) { - if (v->modinst->tag_root) return v; - if (AbstractNode *vroot = find_root_tag(v)) return vroot; - } - return NULL; -} +#include "csgtestcore.h" int main(int argc, char *argv[]) { - if (argc != 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); - exit(1); - } - - const char *filename = argv[1]; - - initialize_builtin_functions(); - initialize_builtin_modules(); - - QApplication app(argc, argv); - - QDir original_path = QDir::current(); - - QString currentdir = QDir::currentPath(); - - QDir libdir(QApplication::instance()->applicationDirPath()); -#ifdef Q_WS_MAC - libdir.cd("../Resources"); // Libraries can be bundled - if (!libdir.exists("libraries")) libdir.cd("../../.."); -#elif defined(Q_OS_UNIX) - if (libdir.cd("../share/openscad/libraries")) { - librarydir = libdir.path(); - } else - if (libdir.cd("../../share/openscad/libraries")) { - librarydir = libdir.path(); - } else - if (libdir.cd("../../libraries")) { - librarydir = libdir.path(); - } else -#endif - if (libdir.cd("libraries")) { - librarydir = libdir.path(); - } - - Context root_ctx; - root_ctx.functions_p = &builtin_functions; - root_ctx.modules_p = &builtin_modules; - root_ctx.set_variable("$fn", Value(0.0)); - root_ctx.set_variable("$fs", Value(1.0)); - root_ctx.set_variable("$fa", Value(12.0)); - root_ctx.set_variable("$t", Value(0.0)); - - Value zero3; - zero3.type = Value::VECTOR; - zero3.append(new Value(0.0)); - zero3.append(new Value(0.0)); - zero3.append(new Value(0.0)); - root_ctx.set_variable("$vpt", zero3); - root_ctx.set_variable("$vpr", zero3); - - - AbstractModule *root_module; - ModuleInstantiation root_inst; - - QFileInfo fileInfo(filename); - handle_dep(filename); - FILE *fp = fopen(filename, "rt"); - if (!fp) { - fprintf(stderr, "Can't open input file `%s'!\n", filename); - exit(1); - } else { - std::stringstream text; - char buffer[513]; - int ret; - while ((ret = fread(buffer, 1, 512, fp)) > 0) { - buffer[ret] = 0; - text << buffer; - } - fclose(fp); - text << commandline_commands; - root_module = parse(text.str().c_str(), fileInfo.absolutePath().toLocal8Bit(), false); - if (!root_module) { - exit(1); - } - } - - QDir::setCurrent(fileInfo.absolutePath()); - - AbstractNode::resetIndexCounter(); - AbstractNode *absolute_root_node = root_module->evaluate(&root_ctx, &root_inst); - AbstractNode *root_node; - // Do we have an explicit root node (! modifier)? - if (!(root_node = find_root_tag(absolute_root_node))) root_node = absolute_root_node; - - Tree tree(root_node); - - CsgInfo csgInfo = CsgInfo(); - - CGALEvaluator cgalevaluator(tree); - CSGTermEvaluator evaluator(tree, &cgalevaluator.psevaluator); - CSGTerm *root_raw_term = evaluator.evaluateCSGTerm(*root_node, csgInfo.highlight_terms, csgInfo.background_terms); - - if (!root_raw_term) { - cerr << "Error: CSG generation failed! (no top level object found)\n"; - return 1; - } - - // CSG normalization - csgInfo.root_norm_term = root_raw_term->link(); - while (1) { - CSGTerm *n = csgInfo.root_norm_term->normalize(); - csgInfo.root_norm_term->unlink(); - if (csgInfo.root_norm_term == n) - break; - csgInfo.root_norm_term = n; - } - - assert(csgInfo.root_norm_term); - - csgInfo.root_chain = new CSGChain(); - csgInfo.root_chain->import(csgInfo.root_norm_term); - fprintf(stderr, "Normalized CSG tree has %d elements\n", csgInfo.root_chain->polysets.size()); - - if (csgInfo.highlight_terms.size() > 0) { - cerr << "Compiling highlights (" << csgInfo.highlight_terms.size() << " CSG Trees)...\n"; - - csgInfo.highlights_chain = new CSGChain(); - for (unsigned int i = 0; i < csgInfo.highlight_terms.size(); i++) { - while (1) { - CSGTerm *n = csgInfo.highlight_terms[i]->normalize(); - csgInfo.highlight_terms[i]->unlink(); - if (csgInfo.highlight_terms[i] == n) - break; - csgInfo.highlight_terms[i] = n; - } - csgInfo.highlights_chain->import(csgInfo.highlight_terms[i]); - } - } - - if (csgInfo.background_terms.size() > 0) { - cerr << "Compiling background (" << csgInfo.background_terms.size() << " CSG Trees)...\n"; - - csgInfo.background_chain = new CSGChain(); - for (unsigned int i = 0; i < csgInfo.background_terms.size(); i++) { - while (1) { - CSGTerm *n = csgInfo.background_terms[i]->normalize(); - csgInfo.background_terms[i]->unlink(); - if (csgInfo.background_terms[i] == n) - break; - csgInfo.background_terms[i] = n; - } - csgInfo.background_chain->import(csgInfo.background_terms[i]); - } - } - - QDir::setCurrent(original_path.absolutePath()); - - /* - fprintf(stderr, "Dump root chain\n"); - cerr << csgInfo.root_chain->fulldump(); - cerr << csgInfo.highlights_chain; - cerr << csgInfo.background_chain; - fprintf(stderr, "end dump\n"); - */ - - ThrownTogetherRenderer thrownTogetherRenderer(csgInfo.root_chain, csgInfo.highlights_chain, csgInfo.background_chain); - csgInfo.glview = new OffscreenView(512,512); - - - - BoundingBox bbox = csgInfo.root_chain->getBoundingBox(); - Vector3d center = (bbox.min() + bbox.max()) / 2; - double radius = (bbox.max() - bbox.min()).norm() / 2; - Vector3d cameradir(1, 1, -0.5); - Vector3d camerapos = center - radius*1.8*cameradir; - csgInfo.glview->setCamera(camerapos, center); - - - csgInfo.glview->setRenderer(&thrownTogetherRenderer); - //OpenCSGRenderer opencsgRenderer(csgInfo.root_chain, csgInfo.highlights_chain, csgInfo.background_chain, csgInfo.glview->shaderinfo); - //csgInfo.glview->setRenderer(&opencsgRenderer); - csgInfo.glview->paintGL(); - csgInfo.glview->save("/dev/stdout"); - - destroy_builtin_functions(); - destroy_builtin_modules(); - + csgtestcore(argc, argv, TEST_OPENCSG); return 0; } diff --git a/tests/test_cmdline_tool.py b/tests/test_cmdline_tool.py index 07afcc87..7d7f23c6 100755 --- a/tests/test_cmdline_tool.py +++ b/tests/test_cmdline_tool.py @@ -24,6 +24,8 @@ import glob import subprocess import re import getopt +import shutil +import platform def initialize_environment(): if not options.generate: options.generate = bool(os.getenv("TEST_GENERATE")) @@ -67,12 +69,56 @@ def compare_default(resultfilename): return False return True +def append_html_output(expectedfilename, resultfilename): + # if html directory & file not there, create them + # copy expected filename and result filename to dir + # append html to show differences + # dump platform.platform() + expectedimg = os.path.basename(expectedfilename) + resultimg = os.path.basename(resultfilename) + template = ''' +

+

+Test command: ///TESTCMD/// Test name: ///TESTNAME///
+
+  
+ Expected:
+ +
+ +
+ Actual:
+ (Platform: ///PLATFORM///)
+ +
+
+
+

+''' + html = template + html = html.replace('///EXPECTED///',expectedimg) + html = html.replace('///RESULT///',resultimg) + html = html.replace('///TESTCMD///',os.path.basename(options.cmd)) + html = html.replace('///TESTNAME///',options.testname) + html = html.replace('///PLATFORM///',platform.platform()) + try: + shutil.copy(expectedfilename,options.imgdiff_dir) + shutil.copy(resultfilename,options.imgdiff_dir) + f = open(options.imgdiff_htmlfile,'a') + f.write(html) + f.close() + print >> sys.stderr, "appended " + options.imgdiff_htmlfile + except: + print >> sys.stderr, "error appending " + options.imgdiff_htmlfile + print >> sys.stderr, sys.exc_info() + def compare_png(resultfilename): if not resultfilename: print >> sys.stderr, "Error: OpenSCAD did not generate an image" return False print >> sys.stderr, 'Yee image compare: ', expectedfilename, ' ', resultfilename if execute_and_redirect("./yee_compare", [expectedfilename, resultfilename], sys.stderr) != 0: + append_html_output(expectedfilename, resultfilename) return False return True @@ -138,6 +184,15 @@ if __name__ == '__main__': options.regressiondir = os.path.join(os.path.split(sys.argv[0])[0], "regression") options.generate = False options.suffix = "txt" + + options.imgdiff_dir = 'imgdiff-fail' + options.imgdiff_htmlfile = os.path.join(options.imgdiff_dir,'failed.html') + try: + if not os.path.isdir(options.imgdiff_dir): + os.mkdir(options.imgdiff_dir) + except: + print >> sys.stderr, "error creating " + options.imgdiff_dir, sys.exc_info() + for o, a in opts: if o in ("-g", "--generate"): options.generate = True elif o in ("-s", "--suffix"): @@ -145,7 +200,7 @@ if __name__ == '__main__': else: options.suffix = a elif o in ("-t", "--test"): options.testname = a - + # and if len(args) < 2: usage() From c2fd035d02f8cf9cea216f34f02dd3f5f6bc5e2a Mon Sep 17 00:00:00 2001 From: Don Bright Date: Tue, 27 Sep 2011 16:47:50 -0500 Subject: [PATCH 09/93] forgot to commit csgtestcore --- tests/csgtestcore.cc | 255 +++++++++++++++++++++++++++++++++++++++++++ tests/csgtestcore.h | 7 ++ tests/opencsgtest.cc | 6 +- 3 files changed, 264 insertions(+), 4 deletions(-) create mode 100644 tests/csgtestcore.cc create mode 100644 tests/csgtestcore.h diff --git a/tests/csgtestcore.cc b/tests/csgtestcore.cc new file mode 100644 index 00000000..efecf5ec --- /dev/null +++ b/tests/csgtestcore.cc @@ -0,0 +1,255 @@ +// csg test core, used by throwntegether test and opencsg test +#include "csgtestcore.h" + +#include +#include "openscad.h" +#include "handle_dep.h" +#include "builtin.h" +#include "context.h" +#include "node.h" +#include "module.h" +#include "polyset.h" +#include "Tree.h" +#include "CSGTermEvaluator.h" +#include "CGALEvaluator.h" +#include "PolySetCGALEvaluator.h" + +#include "OpenCSGRenderer.h" +#include "ThrownTogetherRenderer.h" + +#include "csgterm.h" +#include "OffscreenView.h" + +#include +#include +#include +#include +#include +#include + +using std::cerr; +using std::cout; + +std::string commandline_commands; +QString librarydir; + +//#define DEBUG + +struct CsgInfo +{ + CSGTerm *root_norm_term; // Normalized CSG products + class CSGChain *root_chain; + std::vector highlight_terms; + CSGChain *highlights_chain; + std::vector background_terms; + CSGChain *background_chain; + OffscreenView *glview; +}; + +AbstractNode *find_root_tag(AbstractNode *n) +{ + foreach(AbstractNode *v, n->children) { + if (v->modinst->tag_root) return v; + if (AbstractNode *vroot = find_root_tag(v)) return vroot; + } + return NULL; +} + +int csgtestcore(int argc, char *argv[], test_type_e test_type) +{ + if (argc < 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(1); + } + + const char *filename = argv[1]; + + initialize_builtin_functions(); + initialize_builtin_modules(); + + QApplication app(argc, argv); + + QDir original_path = QDir::current(); + + QString currentdir = QDir::currentPath(); + + QDir libdir(QApplication::instance()->applicationDirPath()); +#ifdef Q_WS_MAC + libdir.cd("../Resources"); // Libraries can be bundled + if (!libdir.exists("libraries")) libdir.cd("../../.."); +#elif defined(Q_OS_UNIX) + if (libdir.cd("../share/openscad/libraries")) { + librarydir = libdir.path(); + } else + if (libdir.cd("../../share/openscad/libraries")) { + librarydir = libdir.path(); + } else + if (libdir.cd("../../libraries")) { + librarydir = libdir.path(); + } else +#endif + if (libdir.cd("libraries")) { + librarydir = libdir.path(); + } + + Context root_ctx; + root_ctx.functions_p = &builtin_functions; + root_ctx.modules_p = &builtin_modules; + root_ctx.set_variable("$fn", Value(0.0)); + root_ctx.set_variable("$fs", Value(1.0)); + root_ctx.set_variable("$fa", Value(12.0)); + root_ctx.set_variable("$t", Value(0.0)); + + Value zero3; + zero3.type = Value::VECTOR; + zero3.append(new Value(0.0)); + zero3.append(new Value(0.0)); + zero3.append(new Value(0.0)); + root_ctx.set_variable("$vpt", zero3); + root_ctx.set_variable("$vpr", zero3); + + + AbstractModule *root_module; + ModuleInstantiation root_inst; + + QFileInfo fileInfo(filename); + handle_dep(filename); + FILE *fp = fopen(filename, "rt"); + if (!fp) { + fprintf(stderr, "Can't open input file `%s'!\n", filename); + exit(1); + } else { + std::stringstream text; + char buffer[513]; + int ret; + while ((ret = fread(buffer, 1, 512, fp)) > 0) { + buffer[ret] = 0; + text << buffer; + } + fclose(fp); + text << commandline_commands; + root_module = parse(text.str().c_str(), fileInfo.absolutePath().toLocal8Bit(), false); + if (!root_module) { + exit(1); + } + } + + QDir::setCurrent(fileInfo.absolutePath()); + + AbstractNode::resetIndexCounter(); + AbstractNode *absolute_root_node = root_module->evaluate(&root_ctx, &root_inst); + AbstractNode *root_node; + // Do we have an explicit root node (! modifier)? + if (!(root_node = find_root_tag(absolute_root_node))) root_node = absolute_root_node; + + Tree tree(root_node); + + CsgInfo csgInfo; + CGALEvaluator cgalevaluator(tree); + CSGTermEvaluator evaluator(tree, &cgalevaluator.psevaluator); + CSGTerm *root_raw_term = evaluator.evaluateCSGTerm(*root_node, + csgInfo.highlight_terms, + csgInfo.background_terms); + + if (!root_raw_term) { + cerr << "Error: CSG generation failed! (no top level object found)\n"; + return 1; + } + + // CSG normalization + csgInfo.root_norm_term = root_raw_term->link(); + while (1) { + CSGTerm *n = csgInfo.root_norm_term->normalize(); + csgInfo.root_norm_term->unlink(); + if (csgInfo.root_norm_term == n) + break; + csgInfo.root_norm_term = n; + } + + assert(csgInfo.root_norm_term); + + csgInfo.root_chain = new CSGChain(); + csgInfo.root_chain->import(csgInfo.root_norm_term); + fprintf(stderr, "Normalized CSG tree has %d elements\n", csgInfo.root_chain->polysets.size()); + + if (csgInfo.highlight_terms.size() > 0) { + cerr << "Compiling highlights (" << csgInfo.highlight_terms.size() << " CSG Trees)...\n"; + + csgInfo.highlights_chain = new CSGChain(); + for (unsigned int i = 0; i < csgInfo.highlight_terms.size(); i++) { + while (1) { + CSGTerm *n = csgInfo.highlight_terms[i]->normalize(); + csgInfo.highlight_terms[i]->unlink(); + if (csgInfo.highlight_terms[i] == n) + break; + csgInfo.highlight_terms[i] = n; + } + csgInfo.highlights_chain->import(csgInfo.highlight_terms[i]); + } + } + + if (csgInfo.background_terms.size() > 0) { + cerr << "Compiling background (" << csgInfo.background_terms.size() << " CSG Trees)...\n"; + + csgInfo.background_chain = new CSGChain(); + for (unsigned int i = 0; i < csgInfo.background_terms.size(); i++) { + while (1) { + CSGTerm *n = csgInfo.background_terms[i]->normalize(); + csgInfo.background_terms[i]->unlink(); + if (csgInfo.background_terms[i] == n) + break; + csgInfo.background_terms[i] = n; + } + csgInfo.background_chain->import(csgInfo.background_terms[i]); + } + } + + QDir::setCurrent(original_path.absolutePath()); + + csgInfo.glview = new OffscreenView(512,512); + BoundingBox bbox = csgInfo.root_chain->getBoundingBox(); + + Vector3d center = (bbox.min() + bbox.max()) / 2; + double radius = (bbox.max() - bbox.min()).norm() / 2; + + + Vector3d cameradir(1, 1, -0.5); + Vector3d camerapos = center - radius*1.8*cameradir; + csgInfo.glview->setCamera(camerapos, center); + + glewInit(); +#ifdef DEBUG + cout << "GLEW version " << glewGetString(GLEW_VERSION) << "\n"; + cout << (const char *)glGetString(GL_RENDERER) << "(" << (const char *)glGetString(GL_VENDOR) << ")\n" + << "OpenGL version " << (const char *)glGetString(GL_VERSION) << "\n"; + cout << "Extensions: " << (const char *)glGetString(GL_EXTENSIONS) << "\n"; + + + if (GLEW_ARB_framebuffer_object) { + cout << "ARB_FBO supported\n"; + } + if (GLEW_EXT_framebuffer_object) { + cout << "EXT_FBO supported\n"; + } + if (GLEW_EXT_packed_depth_stencil) { + cout << "EXT_packed_depth_stencil\n"; + } +#endif + + OpenCSGRenderer opencsgRenderer(csgInfo.root_chain, csgInfo.highlights_chain, csgInfo.background_chain, csgInfo.glview->shaderinfo); + ThrownTogetherRenderer thrownTogetherRenderer(csgInfo.root_chain, csgInfo.highlights_chain, csgInfo.background_chain); + + if (test_type == TEST_THROWNTOGETHER) + csgInfo.glview->setRenderer(&thrownTogetherRenderer); + else + csgInfo.glview->setRenderer(&opencsgRenderer); + + csgInfo.glview->paintGL(); + + csgInfo.glview->save("/dev/stdout"); + + destroy_builtin_functions(); + destroy_builtin_modules(); + + return 0; +} diff --git a/tests/csgtestcore.h b/tests/csgtestcore.h new file mode 100644 index 00000000..b988b217 --- /dev/null +++ b/tests/csgtestcore.h @@ -0,0 +1,7 @@ +enum test_type_e { + TEST_THROWNTOGETHER, + TEST_OPENCSG +}; + +int csgtestcore(int argc, char *argv[], test_type_e test_type); + diff --git a/tests/opencsgtest.cc b/tests/opencsgtest.cc index 2d1f5ac2..9ddf6622 100644 --- a/tests/opencsgtest.cc +++ b/tests/opencsgtest.cc @@ -1,7 +1,5 @@ #include "csgtestcore.h" -int main(int argc, char *argv[]) -{ - csgtestcore(argc, argv, TEST_OPENCSG); - return 0; +int main(int argc, char* argv[]) { + return csgtestcore(argc+1, argv, TEST_OPENCSG); } From 1f67e60eae04977c1d1e3b214c7de141c62988dd Mon Sep 17 00:00:00 2001 From: Don Bright Date: Tue, 27 Sep 2011 16:53:39 -0500 Subject: [PATCH 10/93] fix crash --- tests/csgtestcore.cc | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/csgtestcore.cc b/tests/csgtestcore.cc index efecf5ec..8182d369 100644 --- a/tests/csgtestcore.cc +++ b/tests/csgtestcore.cc @@ -35,8 +35,10 @@ QString librarydir; //#define DEBUG -struct CsgInfo +class CsgInfo { +public: + CsgInfo(); CSGTerm *root_norm_term; // Normalized CSG products class CSGChain *root_chain; std::vector highlight_terms; @@ -46,6 +48,16 @@ struct CsgInfo OffscreenView *glview; }; +CsgInfo::CsgInfo() { + root_norm_term = NULL; + root_chain = NULL; + highlight_terms = vector(); + highlights_chain = NULL; + background_terms = vector(); + background_chain = NULL; + glview = NULL; +} + AbstractNode *find_root_tag(AbstractNode *n) { foreach(AbstractNode *v, n->children) { @@ -144,7 +156,7 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type) Tree tree(root_node); - CsgInfo csgInfo; + CsgInfo csgInfo = CsgInfo(); CGALEvaluator cgalevaluator(tree); CSGTermEvaluator evaluator(tree, &cgalevaluator.psevaluator); CSGTerm *root_raw_term = evaluator.evaluateCSGTerm(*root_node, From 917fb654155d41c5930dc6a70e0fe9b8a9f671bd Mon Sep 17 00:00:00 2001 From: Don Bright Date: Tue, 27 Sep 2011 16:58:03 -0500 Subject: [PATCH 11/93] test throwntogether --- tests/throwntogethertest.cc | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 tests/throwntogethertest.cc diff --git a/tests/throwntogethertest.cc b/tests/throwntogethertest.cc new file mode 100644 index 00000000..ba0a6820 --- /dev/null +++ b/tests/throwntogethertest.cc @@ -0,0 +1,5 @@ +#include "csgtestcore.h" + +int main(int argc, char* argv[]) { + return csgtestcore(argc+1, argv, TEST_THROWNTOGETHER); +} From 6c3ce9934755bcc579ac30104d651608c2c71622 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Wed, 28 Sep 2011 03:04:29 +0200 Subject: [PATCH 12/93] Mac compile fix --- tests/OffscreenContext.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/OffscreenContext.h b/tests/OffscreenContext.h index f1c7123a..a0248320 100644 --- a/tests/OffscreenContext.h +++ b/tests/OffscreenContext.h @@ -1,7 +1,7 @@ #ifndef OFFSCREENCONTEXT_H_ #define OFFSCREENCONTEXT_H_ -#ifdef Q_WS_MAC +#ifdef __APPLE__ #include #else #include From f0772e73bd258f678723e8e040c6cb72285c1d08 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 29 Sep 2011 01:58:12 +0200 Subject: [PATCH 13/93] Refactored offscreen context a bit to get a better overview --- tests/CMakeLists.txt | 6 +- tests/OffscreenContext.cc | 66 +++++++-------------- tests/OffscreenContext.mm | 120 +++++++++----------------------------- tests/csgtestcore.cc | 2 +- 4 files changed, 52 insertions(+), 142 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a0db0e49..e8e8fc6a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -186,7 +186,7 @@ target_link_libraries(cgaltest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${QT_ # # cgalpngtest # -add_executable(cgalpngtest cgalpngtest.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} +add_executable(cgalpngtest cgalpngtest.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc ../src/CGALRenderer.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CSGTermEvaluator.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc @@ -199,7 +199,7 @@ target_link_libraries(cgalpngtest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${ # opencsgtest # -add_executable(opencsgtest opencsgtest.cc csgtestcore.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} +add_executable(opencsgtest opencsgtest.cc csgtestcore.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc ../src/OpenCSGRenderer.cc ../src/ThrownTogetherRenderer.cc ../src/CSGTermEvaluator.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc @@ -212,7 +212,7 @@ target_link_libraries(opencsgtest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${ # throwntogethertest # -add_executable(throwntogethertest throwntogethertest.cc csgtestcore.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} +add_executable(throwntogethertest throwntogethertest.cc csgtestcore.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc ../src/OpenCSGRenderer.cc ../src/ThrownTogetherRenderer.cc ../src/CSGTermEvaluator.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc index 66b4ab30..6f2104f1 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContext.cc @@ -1,6 +1,6 @@ #include "OffscreenContext.h" #include "printutils.h" -#include "lodepng.h" +#include "imageutils.h" // see http://www.gamedev.net/topic/552607-conflict-between-glew-and-sdl/ #define NO_SDL_GLEXT @@ -44,36 +44,6 @@ void write_targa(const char *filename, GLubyte *pixels, int width, int height) } } -void write_png(const char *filename, GLubyte *pixels, int width, int height) -{ - size_t pixel_size = 4; - size_t dataout_size = -1; - GLubyte *dataout = (GLubyte*)malloc(width*height*pixel_size); // freed below - GLubyte *pixels_flipped = (GLubyte*)malloc(width*height*pixel_size); // freed below - for (int y=0;ywidth * ctx->height * samplesPerPixel ]; + GLubyte pixels[ctx->width * ctx->height * samplesPerPixel]; glReadPixels(0, 0, ctx->width, ctx->height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - //char * filename2="blah2.tga"; - //PRINTF("writing %s\n",filename2); - //write_targa(filename2,pixels,ctx->width, ctx->height); - //char * filename2="blah2.png"; - PRINTF("writing %s . . .",filename); - //write_targa(filename2,pixels,ctx->width, ctx->height); - write_png(filename,pixels,ctx->width, ctx->height); - PRINTF("written\n"); - return true; + // Flip it vertically - images read from OpenGL buffers are upside-down + unsigned char *flippedBuffer = (unsigned char *)malloc(rowBytes * ctx->height); + if (!flippedBuffer) { + std::cout << "Unable to allocate flipped buffer for corrected image."; + return 1; + } + flip_image(bufferData, flippedBuffer, samplesPerPixel, ctx->width, ctx->height); + + bool writeok = write_png(filename, flippedBuffer, ctx->width, ctx->height); + + free(flippedBuffer); + free(bufferData); + + return writeok; } void bind_offscreen_context(OffscreenContext *ctx) diff --git a/tests/OffscreenContext.mm b/tests/OffscreenContext.mm index 9a6a5e2e..8797e496 100644 --- a/tests/OffscreenContext.mm +++ b/tests/OffscreenContext.mm @@ -1,4 +1,5 @@ #include "OffscreenContext.h" +#include "imageutils.h" #import #import // for gluCheckExtension @@ -26,37 +27,32 @@ OffscreenContext *create_offscreen_context(int w, int h) ctx->pool = [NSAutoreleasePool new]; - /* - * Create an OpenGL context just so that OpenGL calls will work. I'm not - using it for actual rendering. - */ + // Create an OpenGL context just so that OpenGL calls will work. + // Will not be used for actual rendering. - NSOpenGLPixelFormatAttribute attributes[] = { + NSOpenGLPixelFormatAttribute attributes[] = { NSOpenGLPFAPixelBuffer, NSOpenGLPFANoRecovery, NSOpenGLPFAAccelerated, NSOpenGLPFADepthSize, 24, (NSOpenGLPixelFormatAttribute) 0 }; - NSOpenGLPixelFormat* pixFormat = [[[NSOpenGLPixelFormat - alloc] initWithAttributes:attributes] autorelease]; - // Create the OpenGL context to render with (with color and depth buffers) - ctx->openGLContext = [[NSOpenGLContext alloc] initWithFormat:pixFormat - shareContext:nil]; + NSOpenGLPixelFormat *pixFormat = [[[NSOpenGLPixelFormat alloc] initWithAttributes:attributes] autorelease]; + + // Create and make current the OpenGL context to render with (with color and depth buffers) + ctx->openGLContext = [[NSOpenGLContext alloc] initWithFormat:pixFormat shareContext:nil]; NULL_ERROR_EXIT(ctx->openGLContext, "Unable to create NSOpenGLContext"); [ctx->openGLContext makeCurrentContext]; - /* - * Test if framebuffer objects are supported - */ + // Test if framebuffer objects are supported + // FIXME: Use GLEW const GLubyte* strExt = glGetString(GL_EXTENSIONS); GLboolean fboSupported = gluCheckExtension((const GLubyte*)"GL_EXT_framebuffer_object", strExt); if (!fboSupported) REPORT_ERROR_AND_EXIT("Your system does not support framebuffer extension - unable to render scene"); - /* - * Create an FBO - */ + + // Create an FBO GLuint renderBuffer = 0; GLuint depthBuffer = 0; // Depth buffer to use for depth testing - optional if you're not using depth testing @@ -79,17 +75,16 @@ OffscreenContext *create_offscreen_context(int w, int h) GL_RENDERBUFFER_EXT, renderBuffer); REPORTGLERROR("specifying color render buffer"); - if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != - GL_FRAMEBUFFER_COMPLETE_EXT) + if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) { REPORT_ERROR_AND_EXIT("Problem with OpenGL framebuffer after specifying color render buffer."); + } - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, - GL_RENDERBUFFER_EXT, depthBuffer); + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthBuffer); REPORTGLERROR("specifying depth render buffer"); - if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != - GL_FRAMEBUFFER_COMPLETE_EXT) + if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) { REPORT_ERROR_AND_EXIT("Problem with OpenGL framebuffer after specifying depth render buffer."); + } return ctx; } @@ -109,96 +104,37 @@ bool teardown_offscreen_context(OffscreenContext *ctx) return true; } +/*! + Capture framebuffer from OpenGL and write it to the given filename as PNG. +*/ bool save_framebuffer(OffscreenContext *ctx, const char *filename) { - /* - * Extract the resulting rendering as an image - */ - + // Read pixels from OpenGL int samplesPerPixel = 4; // R, G, B and A int rowBytes = samplesPerPixel * ctx->width; - char* bufferData = (char*)malloc(rowBytes * ctx->height); + unsigned char *bufferData = (unsigned char *)malloc(rowBytes * ctx->height); if (!bufferData) { std::cerr << "Unable to allocate buffer for image extraction."; return 1; } - glReadPixels(0, 0, ctx->width, ctx->height, GL_BGRA, GL_UNSIGNED_BYTE, + glReadPixels(0, 0, ctx->width, ctx->height, GL_RGBA, GL_UNSIGNED_BYTE, bufferData); REPORTGLERROR("reading pixels from framebuffer"); // Flip it vertically - images read from OpenGL buffers are upside-down - char* flippedBuffer = (char*)malloc(rowBytes * ctx->height); + unsigned char *flippedBuffer = (unsigned char *)malloc(rowBytes * ctx->height); if (!flippedBuffer) { std::cout << "Unable to allocate flipped buffer for corrected image."; return 1; } - for (int i = 0 ; i < ctx->height ; i++) { - bcopy(bufferData + i * rowBytes, flippedBuffer + (ctx->height - i - 1) * - rowBytes, rowBytes); - } + flip_image(bufferData, flippedBuffer, samplesPerPixel, ctx->width, ctx->height); - /* - * Output the image to a file - */ - CGColorSpaceRef colorSpace = - CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); - CGBitmapInfo bitmapInfo = kCGImageAlphaNoneSkipFirst | - kCGBitmapByteOrder32Little; // XRGB Little Endian - int bitsPerComponent = 8; - CGContextRef contextRef = CGBitmapContextCreate(flippedBuffer, - ctx->width, ctx->height, bitsPerComponent, rowBytes, - colorSpace, bitmapInfo); - if (!contextRef) { - std::cerr << "Unable to create CGContextRef."; - return false; - } - - CGImageRef imageRef = CGBitmapContextCreateImage(contextRef); - if (!imageRef) { - std::cerr << "Unable to create CGImageRef."; - return false; - } - Boolean isDirectory = false; - CFStringRef fname = CFStringCreateWithCString(kCFAllocatorDefault, filename, kCFStringEncodingUTF8); - CFURLRef fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, - fname, kCFURLPOSIXPathStyle, isDirectory); - if (!fileURL) { - std::cerr << "Unable to create file URL ref."; - return false; - } - CGDataConsumerRef dataconsumer = CGDataConsumerCreateWithURL(fileURL); - - CFIndex fileImageIndex = 1; - CFMutableDictionaryRef fileDict = NULL; - CFStringRef fileUTType = kUTTypePNG; - // Create an image destination opaque reference for authoring an image file - CGImageDestinationRef imageDest = CGImageDestinationCreateWithDataConsumer(dataconsumer, - fileUTType, - fileImageIndex, - fileDict); - if (!imageDest) { - std::cerr << "Unable to create CGImageDestinationRef."; - return false; - } - CFIndex capacity = 1; - CFMutableDictionaryRef imageProps = - CFDictionaryCreateMutable(kCFAllocatorDefault, - capacity, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); - CGImageDestinationAddImage(imageDest, imageRef, imageProps); - CGImageDestinationFinalize(imageDest); + bool writeok = write_png(filename, flippedBuffer, ctx->width, ctx->height); free(flippedBuffer); free(bufferData); - CFRelease(imageDest); - CFRelease(dataconsumer); - CFRelease(fileURL); - CFRelease(fname); - CFRelease(imageProps); - CGColorSpaceRelease(colorSpace); - CGImageRelease(imageRef); - return true; + + return writeok; } void bind_offscreen_context(OffscreenContext *ctx) diff --git a/tests/csgtestcore.cc b/tests/csgtestcore.cc index 8182d369..dd7830ba 100644 --- a/tests/csgtestcore.cc +++ b/tests/csgtestcore.cc @@ -185,7 +185,7 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type) fprintf(stderr, "Normalized CSG tree has %d elements\n", csgInfo.root_chain->polysets.size()); if (csgInfo.highlight_terms.size() > 0) { - cerr << "Compiling highlights (" << csgInfo.highlight_terms.size() << " CSG Trees)...\n"; + cerr << "Compiling highlights (" << csgInfo.highlight_terms.size() << " CSG Trees)...\n"; csgInfo.highlights_chain = new CSGChain(); for (unsigned int i = 0; i < csgInfo.highlight_terms.size(); i++) { From c6e5e84eec401096451f14e9e853e33c3aa88853 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 29 Sep 2011 02:35:24 +0200 Subject: [PATCH 14/93] Linux compile fixes --- tests/OffscreenContext.cc | 8 ++++---- tests/imageutils-lodepng.cc | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc index 6f2104f1..36adc150 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContext.cc @@ -142,24 +142,24 @@ bool teardown_offscreen_context(OffscreenContext *ctx) */ bool save_framebuffer(OffscreenContext *ctx, const char *filename) { - SDL_GL_SwapBuffers(); // show image + SDL_GL_SwapBuffers(); // show image - int samplesPerPixel = 4; // R, G, B and A + int samplesPerPixel = 4; // R, G, B and A GLubyte pixels[ctx->width * ctx->height * samplesPerPixel]; glReadPixels(0, 0, ctx->width, ctx->height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); // Flip it vertically - images read from OpenGL buffers are upside-down + int rowBytes = samplesPerPixel * ctx->width; unsigned char *flippedBuffer = (unsigned char *)malloc(rowBytes * ctx->height); if (!flippedBuffer) { std::cout << "Unable to allocate flipped buffer for corrected image."; return 1; } - flip_image(bufferData, flippedBuffer, samplesPerPixel, ctx->width, ctx->height); + flip_image(pixels, flippedBuffer, samplesPerPixel, ctx->width, ctx->height); bool writeok = write_png(filename, flippedBuffer, ctx->width, ctx->height); free(flippedBuffer); - free(bufferData); return writeok; } diff --git a/tests/imageutils-lodepng.cc b/tests/imageutils-lodepng.cc index 98c48deb..8636fa44 100644 --- a/tests/imageutils-lodepng.cc +++ b/tests/imageutils-lodepng.cc @@ -1,4 +1,5 @@ #include "lodepng.h" +#include bool write_png(const char *filename, unsigned char *pixels, int width, int height) { @@ -6,7 +7,7 @@ bool write_png(const char *filename, unsigned char *pixels, int width, int heigh //LodePNG_Text_add(&encoder.infoPng.text, "Comment", "Created with LodePNG"); size_t dataout_size = -1; - GLubyte *dataout = (GLubyte*)malloc(width*height*4); + unsigned char *dataout = (unsigned char *)malloc(width*height*4); LodePNG_encode(&dataout, &dataout_size, pixels, width, height, LCT_RGBA, 8); //LodePNG_saveFile(dataout, dataout_size, "blah2.png"); FILE *f = fopen(filename, "w"); From 846970462afbe91e3c06f57c45e095a625aec0d3 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 29 Sep 2011 04:05:47 +0200 Subject: [PATCH 15/93] More refactoring of offscreen rendering --- tests/CMakeLists.txt | 6 ++-- tests/OffscreenContext.cc | 56 ----------------------------------- tests/OffscreenContext.h | 9 ------ tests/OffscreenContext.mm | 58 +++++-------------------------------- tests/fboutils.cc | 61 +++++++++++++++++++++++++++++++++++++++ tests/fboutils.h | 10 +++++++ 6 files changed, 82 insertions(+), 118 deletions(-) create mode 100644 tests/fboutils.cc create mode 100644 tests/fboutils.h diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e8e8fc6a..feff2c2d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -186,7 +186,7 @@ target_link_libraries(cgaltest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${QT_ # # cgalpngtest # -add_executable(cgalpngtest cgalpngtest.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc +add_executable(cgalpngtest cgalpngtest.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc fboutils.cc ../src/CGALRenderer.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CSGTermEvaluator.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc @@ -199,7 +199,7 @@ target_link_libraries(cgalpngtest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${ # opencsgtest # -add_executable(opencsgtest opencsgtest.cc csgtestcore.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc +add_executable(opencsgtest opencsgtest.cc csgtestcore.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc fboutils.cc ../src/OpenCSGRenderer.cc ../src/ThrownTogetherRenderer.cc ../src/CSGTermEvaluator.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc @@ -212,7 +212,7 @@ target_link_libraries(opencsgtest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${ # throwntogethertest # -add_executable(throwntogethertest throwntogethertest.cc csgtestcore.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc +add_executable(throwntogethertest throwntogethertest.cc csgtestcore.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc fboutils.cc ../src/OpenCSGRenderer.cc ../src/ThrownTogetherRenderer.cc ../src/CSGTermEvaluator.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc index 36adc150..fa1afde2 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContext.cc @@ -14,10 +14,6 @@ //#include // for gluCheckExtension #include -// Simple error reporting macros to help keep the sample code clean -#define REPORT_ERROR_AND_EXIT(desc) { std::cout << desc << "\n"; return false; } -#define NULL_ERROR_EXIT(test, desc) { if (!test) REPORT_ERROR_AND_EXIT(desc); } - struct OffscreenContext { int width; @@ -58,51 +54,6 @@ OffscreenContext *create_offscreen_context(int w, int h) // but must also come before various EXT calls //glewInit(); -/* - // Test if framebuffer objects are supported - const GLubyte* strExt = glGetString(GL_EXTENSIONS); - GLboolean fboSupported = gluCheckExtension((const GLubyte*)"GL_EXT_framebuffer_object", strExt); - if (!fboSupported) - REPORT_ERROR_AND_EXIT("Your system does not support framebuffer extension - unable to render scene"); - - printf("%i\n", (int)glGenFramebuffersEXT); - GLuint fbo; - //ctx->fbo = 0; - glGenFramebuffersEXT(1, &fbo); - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); - REPORTGLERROR("binding framebuffer"); - - - GLuint renderBuffer = 0; - GLuint depthBuffer = 0; - // Depth buffer to use for depth testing - optional if you're not using depth testing - glGenRenderbuffersEXT(1, &depthBuffer); - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthBuffer); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, w, h); - REPORTGLERROR("creating depth render buffer"); - - // Render buffer to use for imaging - glGenRenderbuffersEXT(1, &renderBuffer); - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderBuffer); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, w, h); - REPORTGLERROR("creating color render buffer"); - - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, - GL_RENDERBUFFER_EXT, renderBuffer); - REPORTGLERROR("specifying color render buffer"); - - if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != - GL_FRAMEBUFFER_COMPLETE_EXT) - REPORT_ERROR_AND_EXIT("Problem with OpenGL framebuffer after specifying color render buffer."); - - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, - GL_RENDERBUFFER_EXT, depthBuffer); - REPORTGLERROR("specifying depth render buffer"); - - if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != - GL_FRAMEBUFFER_COMPLETE_EXT) - REPORT_ERROR_AND_EXIT("Problem with OpenGL framebuffer after specifying depth render buffer."); -*/ /* glClearColor(1, 1, 1, 1); glClear(GL_COLOR_BUFFER_BIT); @@ -128,12 +79,6 @@ OffscreenContext *create_offscreen_context(int w, int h) bool teardown_offscreen_context(OffscreenContext *ctx) { - // "un"bind my FBO -// glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); - - /* - * Cleanup - */ return true; } @@ -166,5 +111,4 @@ bool save_framebuffer(OffscreenContext *ctx, const char *filename) void bind_offscreen_context(OffscreenContext *ctx) { -// glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, ctx->fbo); } diff --git a/tests/OffscreenContext.h b/tests/OffscreenContext.h index a0248320..a079c3f3 100644 --- a/tests/OffscreenContext.h +++ b/tests/OffscreenContext.h @@ -1,17 +1,8 @@ #ifndef OFFSCREENCONTEXT_H_ #define OFFSCREENCONTEXT_H_ -#ifdef __APPLE__ -#include -#else -#include -#include -#endif - #include // for error output -#define REPORTGLERROR(task) { GLenum tGLErr = glGetError(); if (tGLErr != GL_NO_ERROR) { std::cout << "OpenGL error " << tGLErr << " while " << task << "\n"; } } - struct OffscreenContext *create_offscreen_context(int w, int h); void bind_offscreen_context(OffscreenContext *ctx); bool teardown_offscreen_context(OffscreenContext *ctx); diff --git a/tests/OffscreenContext.mm b/tests/OffscreenContext.mm index 8797e496..90af712f 100644 --- a/tests/OffscreenContext.mm +++ b/tests/OffscreenContext.mm @@ -1,14 +1,9 @@ #include "OffscreenContext.h" #include "imageutils.h" +#include "fboutils.h" -#import -#import // for gluCheckExtension #import // for NSOpenGL... -// Simple error reporting macros to help keep the sample code clean -#define REPORT_ERROR_AND_EXIT(desc) { std::cout << desc << "\n"; return false; } -#define NULL_ERROR_EXIT(test, desc) { if (!test) REPORT_ERROR_AND_EXIT(desc); } - struct OffscreenContext { NSOpenGLContext *openGLContext; @@ -41,58 +36,21 @@ OffscreenContext *create_offscreen_context(int w, int h) // Create and make current the OpenGL context to render with (with color and depth buffers) ctx->openGLContext = [[NSOpenGLContext alloc] initWithFormat:pixFormat shareContext:nil]; - NULL_ERROR_EXIT(ctx->openGLContext, "Unable to create NSOpenGLContext"); + if (!ctx->openGLContext) { + std::cerr << "Unable to create NSOpenGLContext\n"; + return NULL; + } [ctx->openGLContext makeCurrentContext]; - // Test if framebuffer objects are supported - // FIXME: Use GLEW - const GLubyte* strExt = glGetString(GL_EXTENSIONS); - GLboolean fboSupported = gluCheckExtension((const GLubyte*)"GL_EXT_framebuffer_object", strExt); - if (!fboSupported) - REPORT_ERROR_AND_EXIT("Your system does not support framebuffer extension - unable to render scene"); - - // Create an FBO - GLuint renderBuffer = 0; - GLuint depthBuffer = 0; - // Depth buffer to use for depth testing - optional if you're not using depth testing - glGenRenderbuffersEXT(1, &depthBuffer); - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthBuffer); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, w, h); - REPORTGLERROR("creating depth render buffer"); - - // Render buffer to use for imaging - glGenRenderbuffersEXT(1, &renderBuffer); - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderBuffer); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, w, h); - REPORTGLERROR("creating color render buffer"); - ctx->fbo = 0; - glGenFramebuffersEXT(1, &ctx->fbo); - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, ctx->fbo); - REPORTGLERROR("binding framebuffer"); - - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, - GL_RENDERBUFFER_EXT, renderBuffer); - REPORTGLERROR("specifying color render buffer"); - - if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) { - REPORT_ERROR_AND_EXIT("Problem with OpenGL framebuffer after specifying color render buffer."); - } - - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthBuffer); - REPORTGLERROR("specifying depth render buffer"); - - if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) { - REPORT_ERROR_AND_EXIT("Problem with OpenGL framebuffer after specifying depth render buffer."); - } + ctx->fbo = fbo_create(w, h); return ctx; } bool teardown_offscreen_context(OffscreenContext *ctx) { - // "un"bind my FBO - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + fbo_unbind(); /* * Cleanup @@ -139,5 +97,5 @@ bool save_framebuffer(OffscreenContext *ctx, const char *filename) void bind_offscreen_context(OffscreenContext *ctx) { - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, ctx->fbo); + fbo_bind(ctx->fbo); } diff --git a/tests/fboutils.cc b/tests/fboutils.cc new file mode 100644 index 00000000..d0430df3 --- /dev/null +++ b/tests/fboutils.cc @@ -0,0 +1,61 @@ +#include "fboutils.h" +#include + +GLuint fbo_create(GLsizei width, GLsizei height) +{ + // Test if framebuffer objects are supported + // FIXME: Use GLEW + const GLubyte* strExt = glGetString(GL_EXTENSIONS); + GLboolean fboSupported = gluCheckExtension((const GLubyte*)"GL_EXT_framebuffer_object", strExt); + if (!fboSupported) { + std::cerr << "Your system does not support framebuffer extension - unable to render scene\n"; + return 0; + } + + // Create an FBO + GLuint fbo = 0; + GLuint renderBuffer = 0; + GLuint depthBuffer = 0; + // Depth buffer to use for depth testing - optional if you're not using depth testing + glGenRenderbuffersEXT(1, &depthBuffer); + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthBuffer); + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, width, height); + REPORTGLERROR("creating depth render buffer"); + + // Render buffer to use for imaging + glGenRenderbuffersEXT(1, &renderBuffer); + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderBuffer); + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, width, height); + REPORTGLERROR("creating color render buffer"); + glGenFramebuffersEXT(1, &fbo); + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); + REPORTGLERROR("binding framebuffer"); + + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_RENDERBUFFER_EXT, renderBuffer); + REPORTGLERROR("specifying color render buffer"); + + if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) { + std::cerr << "Problem with OpenGL framebuffer after specifying color render buffer.\n"; + return 0; + } + + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthBuffer); + REPORTGLERROR("specifying depth render buffer"); + + if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) { + std::cerr << "Problem with OpenGL framebuffer after specifying depth render buffer.\n"; + return 0; + } + return fbo; +} + +void fbo_bind(GLuint fbo) +{ + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); +} + +void fbo_unbind() +{ + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); +} diff --git a/tests/fboutils.h b/tests/fboutils.h new file mode 100644 index 00000000..0e5c32e0 --- /dev/null +++ b/tests/fboutils.h @@ -0,0 +1,10 @@ +#ifndef FBOUTILS_H_ +#define FBOUTILS_H_ + +#include "system-gl.h" + +GLuint fbo_create(GLsizei width, GLsizei height); +void fbo_bind(GLuint fbo); +void fbo_unbind(); + +#endif From 8c94c31dd2e5e85fc13d39fcd57b26df7afefe1e Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 29 Sep 2011 04:05:59 +0200 Subject: [PATCH 16/93] More refactoring of offscreen rendering --- tests/system-gl.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/system-gl.h diff --git a/tests/system-gl.h b/tests/system-gl.h new file mode 100644 index 00000000..ddc2cf24 --- /dev/null +++ b/tests/system-gl.h @@ -0,0 +1,14 @@ +#ifndef SYSTEMGL_H_ +#define SYSTEMGL_H_ + +#ifdef __APPLE__ +#include +#include // for gluCheckExtension +#else +#include +#include +#endif + +#define REPORTGLERROR(task) { GLenum tGLErr = glGetError(); if (tGLErr != GL_NO_ERROR) { std::cout << "OpenGL error " << tGLErr << " while " << task << "\n"; } } + +#endif From 46ccaf371609cf8a32753c75db5dd3eb35ae27c0 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Tue, 4 Oct 2011 21:06:57 +0200 Subject: [PATCH 17/93] merged --- src/linalg.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/linalg.h b/src/linalg.h index 06991cf5..02fa17f7 100644 --- a/src/linalg.h +++ b/src/linalg.h @@ -1,6 +1,11 @@ #ifndef LINALG_H_ #define LINALG_H_ +#ifndef __APPLE__ +#define EIGEN_DONT_VECTORIZE 1 +#define EIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT 1 +#endif + #include #include From 6395435db71d279a83ea4a484fd13a3cf913d593 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Tue, 4 Oct 2011 23:24:32 +0200 Subject: [PATCH 18/93] Clean up the fbo component, fix related issues --- tests/CMakeLists.txt | 6 +-- tests/OffscreenContext.mm | 34 +++++++++++++-- tests/cgalpngtest.cc | 6 ++- tests/csgtestcore.cc | 33 ++++----------- tests/fbo.cc | 87 +++++++++++++++++++++++++++++++++++++++ tests/fbo.h | 23 +++++++++++ tests/fboutils.cc | 61 --------------------------- tests/fboutils.h | 10 ----- tests/system-gl.h | 8 ---- 9 files changed, 155 insertions(+), 113 deletions(-) create mode 100644 tests/fbo.cc create mode 100644 tests/fbo.h delete mode 100644 tests/fboutils.cc delete mode 100644 tests/fboutils.h diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4ef7bf39..eb3e911e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -186,7 +186,7 @@ target_link_libraries(cgaltest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${QT_ # # cgalpngtest # -add_executable(cgalpngtest cgalpngtest.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc fboutils.cc +add_executable(cgalpngtest cgalpngtest.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc fbo.cc ../src/CGALRenderer.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CSGTermEvaluator.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc @@ -199,7 +199,7 @@ target_link_libraries(cgalpngtest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${ # opencsgtest # -add_executable(opencsgtest opencsgtest.cc csgtestcore.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc fboutils.cc +add_executable(opencsgtest opencsgtest.cc csgtestcore.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc fbo.cc ../src/OpenCSGRenderer.cc ../src/ThrownTogetherRenderer.cc ../src/CSGTermEvaluator.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc @@ -212,7 +212,7 @@ target_link_libraries(opencsgtest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${ # throwntogethertest # -add_executable(throwntogethertest throwntogethertest.cc csgtestcore.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc fboutils.cc +add_executable(throwntogethertest throwntogethertest.cc csgtestcore.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc fbo.cc ../src/OpenCSGRenderer.cc ../src/ThrownTogetherRenderer.cc ../src/CSGTermEvaluator.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc diff --git a/tests/OffscreenContext.mm b/tests/OffscreenContext.mm index 90af712f..eb2f7771 100644 --- a/tests/OffscreenContext.mm +++ b/tests/OffscreenContext.mm @@ -1,16 +1,19 @@ #include "OffscreenContext.h" #include "imageutils.h" -#include "fboutils.h" +#include "fbo.h" #import // for NSOpenGL... + +#define REPORTGLERROR(task) { GLenum tGLErr = glGetError(); if (tGLErr != GL_NO_ERROR) { std::cout << "OpenGL error " << tGLErr << " while " << task << "\n"; } } + struct OffscreenContext { NSOpenGLContext *openGLContext; NSAutoreleasePool *pool; int width; int height; - GLuint fbo; + fbo_t *fbo; }; @@ -42,15 +45,38 @@ OffscreenContext *create_offscreen_context(int w, int h) } [ctx->openGLContext makeCurrentContext]; + + glewInit(); +#ifdef DEBUG + cout << "GLEW version " << glewGetString(GLEW_VERSION) << "\n"; + cout << (const char *)glGetString(GL_RENDERER) << "(" << (const char *)glGetString(GL_VENDOR) << ")\n" + << "OpenGL version " << (const char *)glGetString(GL_VERSION) << "\n"; + cout << "Extensions: " << (const char *)glGetString(GL_EXTENSIONS) << "\n"; + + + if (GLEW_ARB_framebuffer_object) { + cout << "ARB_FBO supported\n"; + } + if (GLEW_EXT_framebuffer_object) { + cout << "EXT_FBO supported\n"; + } + if (GLEW_EXT_packed_depth_stencil) { + cout << "EXT_packed_depth_stencil\n"; + } +#endif - ctx->fbo = fbo_create(w, h); + ctx->fbo = fbo_new(); + if (!fbo_init(ctx->fbo, w, h)) { + return NULL; + } return ctx; } bool teardown_offscreen_context(OffscreenContext *ctx) { - fbo_unbind(); + fbo_unbind(ctx->fbo); + fbo_delete(ctx->fbo); /* * Cleanup diff --git a/tests/cgalpngtest.cc b/tests/cgalpngtest.cc index cae5c670..638c088b 100644 --- a/tests/cgalpngtest.cc +++ b/tests/cgalpngtest.cc @@ -181,7 +181,11 @@ int main(int argc, char **argv) // match with csgtest ends csgInfo.glview = new OffscreenView(512,512); - //glewInit(); + GLenum err = glewInit(); + if (GLEW_OK != err) { + fprintf(stderr, "Unable to init GLEW: %s\n", glewGetErrorString(err)); + exit(1); + } #ifdef DEBUG cout << "GLEW version " << glewGetString(GLEW_VERSION) << "\n"; cout << (const char *)glGetString(GL_RENDERER) << "(" << (const char *)glGetString(GL_VENDOR) << ")\n" diff --git a/tests/csgtestcore.cc b/tests/csgtestcore.cc index dd7830ba..6ef05f3e 100644 --- a/tests/csgtestcore.cc +++ b/tests/csgtestcore.cc @@ -1,7 +1,7 @@ // csg test core, used by throwntegether test and opencsg test #include "csgtestcore.h" -#include +#include "system-gl.h" #include "openscad.h" #include "handle_dep.h" #include "builtin.h" @@ -51,9 +51,9 @@ public: CsgInfo::CsgInfo() { root_norm_term = NULL; root_chain = NULL; - highlight_terms = vector(); + highlight_terms = std::vector(); highlights_chain = NULL; - background_terms = vector(); + background_terms = std::vector(); background_chain = NULL; glview = NULL; } @@ -74,7 +74,7 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type) exit(1); } - const char *filename = argv[1]; + std::string filename(argv[1]); initialize_builtin_functions(); initialize_builtin_modules(); @@ -124,11 +124,11 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type) AbstractModule *root_module; ModuleInstantiation root_inst; - QFileInfo fileInfo(filename); + QFileInfo fileInfo(filename.c_str()); handle_dep(filename); - FILE *fp = fopen(filename, "rt"); + FILE *fp = fopen(filename.c_str(), "rt"); if (!fp) { - fprintf(stderr, "Can't open input file `%s'!\n", filename); + fprintf(stderr, "Can't open input file `%s'!\n", filename.c_str()); exit(1); } else { std::stringstream text; @@ -229,25 +229,6 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type) Vector3d camerapos = center - radius*1.8*cameradir; csgInfo.glview->setCamera(camerapos, center); - glewInit(); -#ifdef DEBUG - cout << "GLEW version " << glewGetString(GLEW_VERSION) << "\n"; - cout << (const char *)glGetString(GL_RENDERER) << "(" << (const char *)glGetString(GL_VENDOR) << ")\n" - << "OpenGL version " << (const char *)glGetString(GL_VERSION) << "\n"; - cout << "Extensions: " << (const char *)glGetString(GL_EXTENSIONS) << "\n"; - - - if (GLEW_ARB_framebuffer_object) { - cout << "ARB_FBO supported\n"; - } - if (GLEW_EXT_framebuffer_object) { - cout << "EXT_FBO supported\n"; - } - if (GLEW_EXT_packed_depth_stencil) { - cout << "EXT_packed_depth_stencil\n"; - } -#endif - OpenCSGRenderer opencsgRenderer(csgInfo.root_chain, csgInfo.highlights_chain, csgInfo.background_chain, csgInfo.glview->shaderinfo); ThrownTogetherRenderer thrownTogetherRenderer(csgInfo.root_chain, csgInfo.highlights_chain, csgInfo.background_chain); diff --git a/tests/fbo.cc b/tests/fbo.cc new file mode 100644 index 00000000..2c7abb46 --- /dev/null +++ b/tests/fbo.cc @@ -0,0 +1,87 @@ +#include "fbo.h" +#include +#include + +fbo_t *fbo_new() +{ + fbo_t *fbo = new fbo_t; + fbo->fbo_id = 0; + fbo->old_fbo_id = 0; + fbo->renderbuf_id = 0; + fbo->depthbuf_id = 0; + + return fbo; +} + +#define REPORTGLERROR(task) { GLenum tGLErr = glGetError(); if (tGLErr != GL_NO_ERROR) { std::cout << "OpenGL error " << tGLErr << " while " << task << "\n"; } } + +bool fbo_init(fbo_t *fbo, size_t width, size_t height) +{ + if (!glewIsSupported("GL_ARB_framebuffer_object")) { + fprintf(stderr, "Framebuffer extension not found\n"); + return false; + } + + // Generate and bind FBO + glGenFramebuffersEXT(1, &fbo->fbo_id); + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->fbo_id); + REPORTGLERROR("binding framebuffer"); + + // Generate depth and render buffers + glGenRenderbuffersEXT(1, &fbo->depthbuf_id); + glGenRenderbuffersEXT(1, &fbo->renderbuf_id); + + // Create buffers with correct size + if (!fbo_resize(fbo, width, height)) return false; + + // Attach render and depth buffers + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_RENDERBUFFER_EXT, fbo->renderbuf_id); + REPORTGLERROR("specifying color render buffer"); + + if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) { + fprintf(stderr, "Problem with OpenGL framebuffer after specifying color render buffer.\n"); + return false; + } + + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, + GL_RENDERBUFFER_EXT, fbo->depthbuf_id); + REPORTGLERROR("specifying depth render buffer"); + + if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) { + fprintf(stderr, "Problem with OpenGL framebuffer after specifying depth render buffer.\n"); + return false; + } + + return true; +} + +bool fbo_resize(fbo_t *fbo, size_t width, size_t height) +{ + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo->depthbuf_id); + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, width, height); + REPORTGLERROR("creating depth render buffer"); + + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo->renderbuf_id); + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, width, height); + REPORTGLERROR("creating color render buffer"); + + return true; +} + +void fbo_delete(fbo_t *fbo) +{ + delete fbo; +} + +GLuint fbo_bind(fbo_t *fbo) +{ + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&fbo->old_fbo_id); + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->fbo_id); + return fbo->old_fbo_id; +} + +void fbo_unbind(fbo_t *fbo) +{ + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->old_fbo_id); +} diff --git a/tests/fbo.h b/tests/fbo.h new file mode 100644 index 00000000..1ee90074 --- /dev/null +++ b/tests/fbo.h @@ -0,0 +1,23 @@ +#ifndef FBO_H_ +#define FBO_H_ + +#include "system-gl.h" +#include // size_t + +struct fbo_t +{ + GLuint fbo_id; + GLuint old_fbo_id; + + GLuint renderbuf_id; + GLuint depthbuf_id; +}; + +fbo_t *fbo_new(); +bool fbo_init(fbo_t *fbo, size_t width, size_t height); +bool fbo_resize(fbo_t *fbo, size_t width, size_t height); +void fbo_delete(fbo_t *fbo); +GLuint fbo_bind(fbo_t *fbo); +void fbo_unbind(fbo_t *fbo); + +#endif diff --git a/tests/fboutils.cc b/tests/fboutils.cc deleted file mode 100644 index d0430df3..00000000 --- a/tests/fboutils.cc +++ /dev/null @@ -1,61 +0,0 @@ -#include "fboutils.h" -#include - -GLuint fbo_create(GLsizei width, GLsizei height) -{ - // Test if framebuffer objects are supported - // FIXME: Use GLEW - const GLubyte* strExt = glGetString(GL_EXTENSIONS); - GLboolean fboSupported = gluCheckExtension((const GLubyte*)"GL_EXT_framebuffer_object", strExt); - if (!fboSupported) { - std::cerr << "Your system does not support framebuffer extension - unable to render scene\n"; - return 0; - } - - // Create an FBO - GLuint fbo = 0; - GLuint renderBuffer = 0; - GLuint depthBuffer = 0; - // Depth buffer to use for depth testing - optional if you're not using depth testing - glGenRenderbuffersEXT(1, &depthBuffer); - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthBuffer); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, width, height); - REPORTGLERROR("creating depth render buffer"); - - // Render buffer to use for imaging - glGenRenderbuffersEXT(1, &renderBuffer); - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderBuffer); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, width, height); - REPORTGLERROR("creating color render buffer"); - glGenFramebuffersEXT(1, &fbo); - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); - REPORTGLERROR("binding framebuffer"); - - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, - GL_RENDERBUFFER_EXT, renderBuffer); - REPORTGLERROR("specifying color render buffer"); - - if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) { - std::cerr << "Problem with OpenGL framebuffer after specifying color render buffer.\n"; - return 0; - } - - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthBuffer); - REPORTGLERROR("specifying depth render buffer"); - - if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) { - std::cerr << "Problem with OpenGL framebuffer after specifying depth render buffer.\n"; - return 0; - } - return fbo; -} - -void fbo_bind(GLuint fbo) -{ - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); -} - -void fbo_unbind() -{ - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); -} diff --git a/tests/fboutils.h b/tests/fboutils.h deleted file mode 100644 index 0e5c32e0..00000000 --- a/tests/fboutils.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef FBOUTILS_H_ -#define FBOUTILS_H_ - -#include "system-gl.h" - -GLuint fbo_create(GLsizei width, GLsizei height); -void fbo_bind(GLuint fbo); -void fbo_unbind(); - -#endif diff --git a/tests/system-gl.h b/tests/system-gl.h index ddc2cf24..0f983cfb 100644 --- a/tests/system-gl.h +++ b/tests/system-gl.h @@ -1,14 +1,6 @@ #ifndef SYSTEMGL_H_ #define SYSTEMGL_H_ -#ifdef __APPLE__ -#include -#include // for gluCheckExtension -#else #include -#include -#endif - -#define REPORTGLERROR(task) { GLenum tGLErr = glGetError(); if (tGLErr != GL_NO_ERROR) { std::cout << "OpenGL error " << tGLErr << " while " << task << "\n"; } } #endif From 77b482f4a86af2c11cc51a7daffad1d5a011e3ca Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Tue, 4 Oct 2011 23:24:53 +0200 Subject: [PATCH 19/93] compile fix after recent Eigen refactoring --- src/csgterm.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/csgterm.cc b/src/csgterm.cc index 0f56b82d..c0eb2c88 100644 --- a/src/csgterm.cc +++ b/src/csgterm.cc @@ -239,7 +239,7 @@ std::string CSGChain::fulldump() dump << " labels: " << labels.size(); dump << "\ndata: \n"; for (size_t i = 0; i < polysets.size(); i++) dump << (*polysets[i]).polygons.size() << "\n"; - for (size_t i = 0; i < matrices.size(); i++) dump << *matrices[i] << "\n"; + for (size_t i = 0; i < matrices.size(); i++) dump << matrices[i].matrix() << "\n"; for (size_t i = 0; i < colors.size(); i++) dump << *colors[i] << "\n"; for (size_t i = 0; i < types.size(); i++) dump << types[i] << "\n"; for (size_t i = 0; i < labels.size(); i++) dump << labels[i] << "\n"; From 872fc643218ef642544612954272e89629e145c5 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 6 Oct 2011 01:51:54 +0200 Subject: [PATCH 20/93] We don't need to use the EXT version of FBO calls --- tests/fbo.cc | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/fbo.cc b/tests/fbo.cc index 2c7abb46..403a32e8 100644 --- a/tests/fbo.cc +++ b/tests/fbo.cc @@ -23,32 +23,32 @@ bool fbo_init(fbo_t *fbo, size_t width, size_t height) } // Generate and bind FBO - glGenFramebuffersEXT(1, &fbo->fbo_id); - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->fbo_id); + glGenFramebuffers(1, &fbo->fbo_id); + glBindFramebuffer(GL_FRAMEBUFFER, fbo->fbo_id); REPORTGLERROR("binding framebuffer"); // Generate depth and render buffers - glGenRenderbuffersEXT(1, &fbo->depthbuf_id); - glGenRenderbuffersEXT(1, &fbo->renderbuf_id); + glGenRenderbuffers(1, &fbo->depthbuf_id); + glGenRenderbuffers(1, &fbo->renderbuf_id); // Create buffers with correct size if (!fbo_resize(fbo, width, height)) return false; // Attach render and depth buffers - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, - GL_RENDERBUFFER_EXT, fbo->renderbuf_id); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, fbo->renderbuf_id); REPORTGLERROR("specifying color render buffer"); - if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) { + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { fprintf(stderr, "Problem with OpenGL framebuffer after specifying color render buffer.\n"); return false; } - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, - GL_RENDERBUFFER_EXT, fbo->depthbuf_id); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + GL_RENDERBUFFER, fbo->depthbuf_id); REPORTGLERROR("specifying depth render buffer"); - if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) { + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { fprintf(stderr, "Problem with OpenGL framebuffer after specifying depth render buffer.\n"); return false; } @@ -58,12 +58,12 @@ bool fbo_init(fbo_t *fbo, size_t width, size_t height) bool fbo_resize(fbo_t *fbo, size_t width, size_t height) { - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo->depthbuf_id); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, width, height); + glBindRenderbuffer(GL_RENDERBUFFER, fbo->depthbuf_id); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); REPORTGLERROR("creating depth render buffer"); - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo->renderbuf_id); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, width, height); + glBindRenderbuffer(GL_RENDERBUFFER, fbo->renderbuf_id); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height); REPORTGLERROR("creating color render buffer"); return true; @@ -77,11 +77,11 @@ void fbo_delete(fbo_t *fbo) GLuint fbo_bind(fbo_t *fbo) { glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&fbo->old_fbo_id); - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->fbo_id); + glBindFramebuffer(GL_FRAMEBUFFER, fbo->fbo_id); return fbo->old_fbo_id; } void fbo_unbind(fbo_t *fbo) { - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->old_fbo_id); + glBindFramebuffer(GL_FRAMEBUFFER, fbo->old_fbo_id); } From 589991c41e5420e68a8504670c25b0dba9e6628e Mon Sep 17 00:00:00 2001 From: Don Bright Date: Sat, 8 Oct 2011 20:02:40 -0500 Subject: [PATCH 21/93] Linux offscreen OpenGL using glxpixmaps. also fix FBO ARB/EXT issues --- doc/testing.txt | 11 ++- tests/CMakeLists.txt | 15 --- tests/OffscreenContext.cc | 202 ++++++++++++++++++++++++++++---------- tests/OffscreenView.cc | 5 +- tests/cgalpngtest.cc | 7 +- tests/csgtestcore.cc | 7 +- tests/fbo.cc | 142 ++++++++++++++++++++++++--- 7 files changed, 302 insertions(+), 87 deletions(-) diff --git a/doc/testing.txt b/doc/testing.txt index 26000c6a..e6645fce 100644 --- a/doc/testing.txt +++ b/doc/testing.txt @@ -31,14 +31,17 @@ be ignored for the test apps in question. Troubleshooting a failed test: ------------------------------ -You can run a single test by running - $ ctest -R testname +You can run a single test by passing the test name to ctest: + $ ctest -R throwntogethertest_sphere -You can run a series by running +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 - + Logs of test runs are found in 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 eb3e911e..9e6640ed 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -57,21 +57,6 @@ if (NOT OPENCSG_INCLUDE_DIR) endif() include_directories(${OPENCSG_INCLUDE_DIR}) -# SDL (for OpenCSG on Linux & other platforms) -if (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") - message(STATU "SDL not needed for Mac OSX") -else() - find_package(SDL REQUIRED) - if (NOT SDL_FOUND) - message(FATAL_ERROR "SDL not found.") - else() - message(STATUS "SDL library found in " ${SDL_LIBRARY}) - message(STATUS "SDL header found in " ${SDL_INCLUDE_DIR}) - set(OPENGL_LIBRARY ${OPENGL_LIBRARY} ${SDL_LIBRARY}) - endif() -endif() -include_directories(${SDL_INCLUDE_DIR}) - # GLEW if (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc index fa1afde2..775491e2 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContext.cc @@ -1,43 +1,145 @@ +/* + +Create an OpenGL context without creating an OpenGL Window. for Linux. + +based on + + http://www.opengl.org/sdk/docs/man/xhtml/glXIntro.xml + http://www.mesa3d.org/brianp/sig97/offscrn.htm + http://glprogramming.com/blue/ch07.html + OffscreenContext.mm (Mac OSX version) + +*/ + #include "OffscreenContext.h" #include "printutils.h" #include "imageutils.h" +#include "fbo.h" -// see http://www.gamedev.net/topic/552607-conflict-between-glew-and-sdl/ -#define NO_SDL_GLEXT -#include - -#define GL_GLEXT_PROTOTYPES #include -#include +#include -//#include -//#include // for gluCheckExtension -#include +#define REPORTGLERROR(task) { GLenum tGLErr = glGetError(); if (tGLErr != GL_NO_ERROR) { std::cout << "OpenGL error " << tGLErr << " while " << task << "\n"; } } struct OffscreenContext { + GLXContext openGLContext; + Display *xDisplay; + GLXPixmap glx_pixmap; + Pixmap x11_pixmap; int width; int height; - GLuint fbo; - GLuint colorbo; - GLuint depthbo; + fbo_t *fbo; }; -void write_targa(const char *filename, GLubyte *pixels, int width, int height) +Bool glx_1_3_pixmap_dummy_context(OffscreenContext *ctx, Bool hybrid) { - FILE *f = fopen( filename, "w" ); - int y; - if (f) { - GLubyte header[] = { - 00,00,02, 00,00,00, 00,00,00, 00,00,00, - 0xff & width, 0xff & width >> 8, - 0xff & height, 0xff & height >> 8, - 32, 0x20 }; // next-to-last = bit depth - fwrite( header, sizeof(header), 1, f); - for (y=height-1; y>=0; y--) - fwrite( pixels + y*width*4, 4, width, f); - fclose(f); + XVisualInfo *vInfo; + GLXFBConfig *fbConfigs; + + int numReturned; + int result; + int dummyAttributes_1_3[] = { + GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT, + GLX_RENDER_TYPE, GLX_RGBA_BIT, + None + }; + + fbConfigs = glXChooseFBConfig( ctx->xDisplay, + DefaultScreen(ctx->xDisplay), + dummyAttributes_1_3, &numReturned ); + if ( fbConfigs == NULL ) { + REPORTGLERROR("glXChooseFBConfig failed") ; + return False; } + + vInfo = glXGetVisualFromFBConfig( ctx->xDisplay, fbConfigs[0] ); + if ( vInfo == NULL ) { + REPORTGLERROR("glXGetVisualFromFBConfig failed") ; + return False; + } + + ctx->x11_pixmap = XCreatePixmap( ctx->xDisplay, DefaultRootWindow(ctx->xDisplay) , 10, 10, 8 ); + + if (hybrid) { + // GLX 1.2 - prevent Mesa warning + ctx->glx_pixmap = glXCreateGLXPixmap( ctx->xDisplay, vInfo, ctx->x11_pixmap ); + } else { + // GLX 1.3 + ctx->glx_pixmap = glXCreatePixmap( ctx->xDisplay, fbConfigs[0], ctx->x11_pixmap, NULL ); // GLX 1.3 + } + + ctx->openGLContext = glXCreateNewContext( ctx->xDisplay, fbConfigs[0], GLX_RGBA_TYPE, NULL, True ); + if ( ctx->openGLContext == NULL ) { + REPORTGLERROR("glXCreateNewContext failed" ); + return False; + } + + result = glXMakeContextCurrent( ctx->xDisplay, ctx->glx_pixmap, ctx->glx_pixmap, ctx->openGLContext ); + if ( result == False ) { + REPORTGLERROR("glXMakeContextCurrent failed" ); + return False; + } + + return True; +} + +Bool make_glx_dummy_context(OffscreenContext *ctx) +{ + /* + Before opening a framebuffer, an OpenGL context must be created. + For GLX, you can do this by creating a 'Pixmap' drawable then + creating the Context off of that. The Pixmap is then never used. + */ + int major; + int minor; + + ctx->xDisplay = XOpenDisplay( NULL ); + if ( ctx->xDisplay == NULL ) { + fprintf(stderr, "Unable to open a connection to the X server\n" ); + return False; + } + + /* + On some systems, the GLX library will report that it is version + 1.2, but some 1.3 functions will be implemented, and, furthermore, + some 1.2 functions won't work right, while the 1.3 functions will, + but glXCreatePixmp will still generate a MESA GLX 1.3 runtime warning. + + To workaround this, detect the situation and use 'hybrid' mix of + 1.2 and 1.3 as needed. + */ + glXQueryVersion(ctx->xDisplay, &major, &minor); + + if (major==1 && minor<=2) { + if (glXCreatePixmap!=NULL) { // 1.3 function exists, even though its 1.2 + return glx_1_3_pixmap_dummy_context(ctx,True); + } else { + fprintf(stderr,"OpenGL error: GLX version 1.3 functions missing. Your GLX: %i.%i\n",major,minor); + return False; + } + } else if (major>=1 && minor>=3) { + return glx_1_3_pixmap_dummy_context(ctx,False); + } +} + +void glewCheck() { +#ifdef DEBUG + cout << "GLEW version " << glewGetString(GLEW_VERSION) << "\n"; + cout << (const char *)glGetString(GL_RENDERER) << "(" << (const char *)glGetString(GL_VENDOR) << ")\n" + << "OpenGL version " << (const char *)glGetString(GL_VERSION) << "\n"; + cout << "Extensions: " << (const char *)glGetString(GL_EXTENSIONS) << "\n"; + + if (GLEW_ARB_framebuffer_object) { + cout << "ARB_FBO supported\n"; + } + if (GLEW_EXT_framebuffer_object) { + cout << "EXT_FBO supported\n"; + } + if (GLEW_EXT_packed_depth_stencil) { + cout << "EXT_packed_depth_stencil\n"; + } +#endif } OffscreenContext *create_offscreen_context(int w, int h) @@ -45,41 +147,36 @@ OffscreenContext *create_offscreen_context(int w, int h) OffscreenContext *ctx = new OffscreenContext; ctx->width = w; ctx->height = h; + ctx->openGLContext = NULL; + ctx->xDisplay = NULL; + ctx->glx_pixmap = NULL; + ctx->x11_pixmap = NULL; + ctx->fbo = NULL; - // dummy window - SDL_Init(SDL_INIT_VIDEO); - SDL_SetVideoMode(ctx->width,ctx->height,32,SDL_OPENGL); + // fill ctx->xDisplay, ctx->openGLContext, x11_pixmap, glx_pixmap + if (!make_glx_dummy_context(ctx)) { + return NULL; + } - // must come after openGL context init (done by dummy window) - // but must also come before various EXT calls - //glewInit(); + glewInit(); //must come after Context creation and before FBO calls. + glewCheck(); -/* - glClearColor(1, 1, 1, 1); - glClear(GL_COLOR_BUFFER_BIT); - glBegin(GL_TRIANGLES); - glColor3f( 1, 0, 0); - glVertex3f( 0, 0, 0); - glVertex3f( 1, 0, 0); - glVertex3f( 0, 1, 0); - glEnd(); - SDL_GL_SwapBuffers(); -// sleep(2); -*/ + ctx->fbo = fbo_new(); + if (!fbo_init(ctx->fbo, w, h)) { + return NULL; + } - int samplesPerPixel = 4; // R, G, B and A - -/* char * filename = "blah.tga"; - GLubyte pixels[ ctx->width * ctx->height * samplesPerPixel ]; - glReadPixels(0, 0, ctx->width, ctx->height, GL_BGRA, GL_UNSIGNED_BYTE, pixels); - printf("writing %s\n",filename); - write_targa(filename,pixels,ctx->width, ctx->height);*/ return ctx; } bool teardown_offscreen_context(OffscreenContext *ctx) { - return true; + fbo_unbind(ctx->fbo); + fbo_delete(ctx->fbo); + glXDestroyPixmap(ctx->xDisplay, ctx->glx_pixmap ); + XFreePixmap(ctx->xDisplay, ctx->x11_pixmap ); + glXDestroyContext( ctx->xDisplay, ctx->openGLContext ); + return true; } /*! @@ -87,8 +184,6 @@ bool teardown_offscreen_context(OffscreenContext *ctx) */ bool save_framebuffer(OffscreenContext *ctx, const char *filename) { - SDL_GL_SwapBuffers(); // show image - int samplesPerPixel = 4; // R, G, B and A GLubyte pixels[ctx->width * ctx->height * samplesPerPixel]; glReadPixels(0, 0, ctx->width, ctx->height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); @@ -111,4 +206,5 @@ bool save_framebuffer(OffscreenContext *ctx, const char *filename) void bind_offscreen_context(OffscreenContext *ctx) { + fbo_bind(ctx->fbo); } diff --git a/tests/OffscreenView.cc b/tests/OffscreenView.cc index 8a4b57de..d188d0de 100644 --- a/tests/OffscreenView.cc +++ b/tests/OffscreenView.cc @@ -14,7 +14,8 @@ OffscreenView::OffscreenView(size_t width, size_t height) object_rot(35, 0, 25), camera_eye(0, 0, 0), camera_center(0, 0, 0) { for (int i = 0; i < 10; i++) this->shaderinfo[i] = 0; - this->ctx = create_offscreen_context(width, height); + this->ctx = create_offscreen_context(width, height); + if ( this->ctx == NULL ) throw -1; initializeGL(); resizeGL(width, height); } @@ -185,7 +186,7 @@ void OffscreenView::paintGL() glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - glClearColor(1.0, 1.0, 0.92, 0.0); + glClearColor(1.0, 1.0, 0.92, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); diff --git a/tests/cgalpngtest.cc b/tests/cgalpngtest.cc index 638c088b..447df456 100644 --- a/tests/cgalpngtest.cc +++ b/tests/cgalpngtest.cc @@ -179,7 +179,12 @@ int main(int argc, char **argv) QDir::setCurrent(original_path.absolutePath()); // match with csgtest ends - csgInfo.glview = new OffscreenView(512,512); + try { + csgInfo.glview = new OffscreenView(512,512); + } catch (int error) { + fprintf(stderr,"Can't create OpenGL OffscreenView. exiting.\n"); + exit(1); + } GLenum err = glewInit(); if (GLEW_OK != err) { diff --git a/tests/csgtestcore.cc b/tests/csgtestcore.cc index 6ef05f3e..2e28bf60 100644 --- a/tests/csgtestcore.cc +++ b/tests/csgtestcore.cc @@ -218,7 +218,12 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type) QDir::setCurrent(original_path.absolutePath()); - csgInfo.glview = new OffscreenView(512,512); + try { + csgInfo.glview = new OffscreenView(512,512); + } catch (int error) { + fprintf(stderr,"Can't create OpenGL OffscreenView. exiting.\n"); + exit(1); + } BoundingBox bbox = csgInfo.root_chain->getBoundingBox(); Vector3d center = (bbox.min() + bbox.max()) / 2; diff --git a/tests/fbo.cc b/tests/fbo.cc index 403a32e8..b7bf1c00 100644 --- a/tests/fbo.cc +++ b/tests/fbo.cc @@ -15,13 +15,94 @@ fbo_t *fbo_new() #define REPORTGLERROR(task) { GLenum tGLErr = glGetError(); if (tGLErr != GL_NO_ERROR) { std::cout << "OpenGL error " << tGLErr << " while " << task << "\n"; } } -bool fbo_init(fbo_t *fbo, size_t width, size_t height) +bool use_ext() { - if (!glewIsSupported("GL_ARB_framebuffer_object")) { - fprintf(stderr, "Framebuffer extension not found\n"); + // do we need to use the EXT or ARB version? + if (!glewIsSupported("GL_ARB_framebuffer_object") && + glewIsSupported("GL_EXT_framebuffer_object")) { + return true; + } else { + return false; + } +} + +bool check_fbo_status() +{ + /* This code is based on user V-man code from + http://www.opengl.org/wiki/GL_EXT_framebuffer_multisample + See also: http://www.songho.ca/opengl/gl_fbo.html */ + GLenum status; + bool result = false; + if (use_ext()) + status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); + else + status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + if (glGetError() != GL_NO_ERROR) + fprintf(stderr, "OpenGL Error %i\n",glGetError()); + + if (status == GL_FRAMEBUFFER_COMPLETE) + result = true; + else if (status == GL_FRAMEBUFFER_UNSUPPORTED) + fprintf(stderr, "GL_FRAMEBUFFER_UNSUPPORTED\n"); + else if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) + fprintf(stderr, "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT\n"); + else if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) + fprintf(stderr, "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT\n"); + else if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT) + fprintf(stderr, "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT\n"); + else if (status == GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT) + fprintf(stderr, "GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT\n"); + else if (status == GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT) + fprintf(stderr, "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT\n"); + else if (status == GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT) + fprintf(stderr, "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT\n"); + else if (status == GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT) + fprintf(stderr, "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT\n"); + else + fprintf(stderr, "Unknown Code: glCheckFramebufferStatusEXT returned %i\n",status); + return result; +} + +bool fbo_ext_init(fbo_t *fbo, size_t width, size_t height) +{ + // Generate and bind FBO + glGenFramebuffersEXT(1, &fbo->fbo_id); + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->fbo_id); + REPORTGLERROR("binding framebuffer"); + + // Generate depth and render buffers + glGenRenderbuffersEXT(1, &fbo->depthbuf_id); + glGenRenderbuffersEXT(1, &fbo->renderbuf_id); + + // Create buffers with correct size + if (!fbo_resize(fbo, width, height)) return false; + + // Attach render and depth buffers + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_RENDERBUFFER_EXT, fbo->renderbuf_id); + REPORTGLERROR("specifying color render buffer"); + + + if (!check_fbo_status()) { + fprintf(stderr, "Problem with OpenGL framebuffer after specifying color render buffer.\n"); return false; } + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, + GL_RENDERBUFFER_EXT, fbo->depthbuf_id); + REPORTGLERROR("specifying depth render buffer"); + + if (!check_fbo_status()) { + fprintf(stderr, "Problem with OpenGL framebuffer after specifying depth render buffer.\n"); + return false; + } + + return true; +} + +bool fbo_arb_init(fbo_t *fbo, size_t width, size_t height) +{ // Generate and bind FBO glGenFramebuffers(1, &fbo->fbo_id); glBindFramebuffer(GL_FRAMEBUFFER, fbo->fbo_id); @@ -56,15 +137,48 @@ bool fbo_init(fbo_t *fbo, size_t width, size_t height) return true; } + +bool fbo_init(fbo_t *fbo, size_t width, size_t height) +{ + /* + Some OpenGL drivers include the framebuffer functions but not with + core or ARB names, only with the EXT name. This has been worked-around + by deciding at runtime, using GLEW, which version needs to be used. See also: + + http://www.opengl.org/wiki/Framebuffer_Object + http://stackoverflow.com/questions/6912988/glgenframebuffers-or-glgenframebuffersex + http://www.devmaster.net/forums/showthread.php?t=10967 + */ + + bool result = false; + if (glewIsSupported("GL_ARB_framebuffer_object")) + result = fbo_arb_init(fbo, width, height); + else if (use_ext()) + result = fbo_ext_init(fbo, width, height); + else + fprintf(stderr, "Framebuffer Object extension not found by GLEW\n"); + return result; +} + bool fbo_resize(fbo_t *fbo, size_t width, size_t height) { - glBindRenderbuffer(GL_RENDERBUFFER, fbo->depthbuf_id); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); - REPORTGLERROR("creating depth render buffer"); + if (use_ext()) { + glBindRenderbufferEXT(GL_RENDERBUFFER, fbo->depthbuf_id); + glRenderbufferStorageEXT(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); + REPORTGLERROR("creating depth render buffer"); - glBindRenderbuffer(GL_RENDERBUFFER, fbo->renderbuf_id); - glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height); - REPORTGLERROR("creating color render buffer"); + glBindRenderbufferEXT(GL_RENDERBUFFER, fbo->renderbuf_id); + glRenderbufferStorageEXT(GL_RENDERBUFFER, GL_RGBA8, width, height); + REPORTGLERROR("creating color render buffer"); + } else { + glBindRenderbuffer(GL_RENDERBUFFER, fbo->depthbuf_id); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); + REPORTGLERROR("creating depth render buffer"); + + glBindRenderbuffer(GL_RENDERBUFFER, fbo->renderbuf_id); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height); + REPORTGLERROR("creating color render buffer"); + } return true; } @@ -77,11 +191,17 @@ void fbo_delete(fbo_t *fbo) GLuint fbo_bind(fbo_t *fbo) { glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&fbo->old_fbo_id); - glBindFramebuffer(GL_FRAMEBUFFER, fbo->fbo_id); + if (use_ext()) + glBindFramebufferEXT(GL_FRAMEBUFFER, fbo->fbo_id); + else + glBindFramebuffer(GL_FRAMEBUFFER, fbo->fbo_id); return fbo->old_fbo_id; } void fbo_unbind(fbo_t *fbo) { - glBindFramebuffer(GL_FRAMEBUFFER, fbo->old_fbo_id); + if (use_ext()) + glBindFramebufferEXT(GL_FRAMEBUFFER, fbo->old_fbo_id); + else + glBindFramebuffer(GL_FRAMEBUFFER, fbo->old_fbo_id); } From b31e2713d2214d4ffeabdd70aa3388af8611ed7f Mon Sep 17 00:00:00 2001 From: notroot Date: Sun, 9 Oct 2011 08:26:48 -0400 Subject: [PATCH 22/93] improve error reporting, prevent running when FBO doesn't bind --- tests/OffscreenContext.cc | 28 ++++++++--------- tests/fbo.cc | 66 ++++++++++++++++++++++----------------- 2 files changed, 51 insertions(+), 43 deletions(-) diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc index 775491e2..f1046ba6 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContext.cc @@ -19,7 +19,7 @@ based on #include #include -#define REPORTGLERROR(task) { GLenum tGLErr = glGetError(); if (tGLErr != GL_NO_ERROR) { std::cout << "OpenGL error " << tGLErr << " while " << task << "\n"; } } +using namespace std; struct OffscreenContext { @@ -49,13 +49,13 @@ Bool glx_1_3_pixmap_dummy_context(OffscreenContext *ctx, Bool hybrid) DefaultScreen(ctx->xDisplay), dummyAttributes_1_3, &numReturned ); if ( fbConfigs == NULL ) { - REPORTGLERROR("glXChooseFBConfig failed") ; + cerr << "GLX error: glXChooseFBConfig failed"; return False; } vInfo = glXGetVisualFromFBConfig( ctx->xDisplay, fbConfigs[0] ); if ( vInfo == NULL ) { - REPORTGLERROR("glXGetVisualFromFBConfig failed") ; + cerr << "GLX error: glXGetVisualFromFBConfig failed"; return False; } @@ -71,13 +71,13 @@ Bool glx_1_3_pixmap_dummy_context(OffscreenContext *ctx, Bool hybrid) ctx->openGLContext = glXCreateNewContext( ctx->xDisplay, fbConfigs[0], GLX_RGBA_TYPE, NULL, True ); if ( ctx->openGLContext == NULL ) { - REPORTGLERROR("glXCreateNewContext failed" ); + cerr << "GLX error: glXCreateNewContext failed"; return False; } result = glXMakeContextCurrent( ctx->xDisplay, ctx->glx_pixmap, ctx->glx_pixmap, ctx->openGLContext ); if ( result == False ) { - REPORTGLERROR("glXMakeContextCurrent failed" ); + cerr << "GLX error: glXMakeContextCurrent failed"; return False; } @@ -96,7 +96,7 @@ Bool make_glx_dummy_context(OffscreenContext *ctx) ctx->xDisplay = XOpenDisplay( NULL ); if ( ctx->xDisplay == NULL ) { - fprintf(stderr, "Unable to open a connection to the X server\n" ); + cerr << "Unable to open a connection to the X server\n"; return False; } @@ -115,7 +115,7 @@ Bool make_glx_dummy_context(OffscreenContext *ctx) if (glXCreatePixmap!=NULL) { // 1.3 function exists, even though its 1.2 return glx_1_3_pixmap_dummy_context(ctx,True); } else { - fprintf(stderr,"OpenGL error: GLX version 1.3 functions missing. Your GLX: %i.%i\n",major,minor); + cerr << "OpenGL error: GLX version 1.3 functions missing. Your GLX: " << major << "." << minor << endl; return False; } } else if (major>=1 && minor>=3) { @@ -125,19 +125,19 @@ Bool make_glx_dummy_context(OffscreenContext *ctx) void glewCheck() { #ifdef DEBUG - cout << "GLEW version " << glewGetString(GLEW_VERSION) << "\n"; - cout << (const char *)glGetString(GL_RENDERER) << "(" << (const char *)glGetString(GL_VENDOR) << ")\n" + cerr << "GLEW version " << glewGetString(GLEW_VERSION) << "\n"; + cerr << (const char *)glGetString(GL_RENDERER) << "(" << (const char *)glGetString(GL_VENDOR) << ")\n" << "OpenGL version " << (const char *)glGetString(GL_VERSION) << "\n"; - cout << "Extensions: " << (const char *)glGetString(GL_EXTENSIONS) << "\n"; + cerr << "Extensions: " << (const char *)glGetString(GL_EXTENSIONS) << "\n"; if (GLEW_ARB_framebuffer_object) { - cout << "ARB_FBO supported\n"; + cerr << "ARB_FBO supported\n"; } if (GLEW_EXT_framebuffer_object) { - cout << "EXT_FBO supported\n"; + cerr << "EXT_FBO supported\n"; } if (GLEW_EXT_packed_depth_stencil) { - cout << "EXT_packed_depth_stencil\n"; + cerr << "EXT_packed_depth_stencil\n"; } #endif } @@ -192,7 +192,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::cout << "Unable to allocate flipped buffer for corrected image."; + std::cerr << "Unable to allocate flipped buffer for corrected image."; return 1; } flip_image(pixels, flippedBuffer, samplesPerPixel, ctx->width, ctx->height); diff --git a/tests/fbo.cc b/tests/fbo.cc index b7bf1c00..f3d92236 100644 --- a/tests/fbo.cc +++ b/tests/fbo.cc @@ -1,6 +1,7 @@ #include "fbo.h" #include #include +using namespace std; fbo_t *fbo_new() { @@ -13,7 +14,15 @@ fbo_t *fbo_new() return fbo; } -#define REPORTGLERROR(task) { GLenum tGLErr = glGetError(); if (tGLErr != GL_NO_ERROR) { std::cout << "OpenGL error " << tGLErr << " while " << task << "\n"; } } +bool REPORTGLERROR(const char * task) +{ + GLenum tGLErr = glGetError(); + if (tGLErr != GL_NO_ERROR) { + std::cerr << "OpenGL error " << tGLErr << " while " << task << "\n"; + return true; + } else + return false; +} bool use_ext() { @@ -38,29 +47,28 @@ bool check_fbo_status() else status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (glGetError() != GL_NO_ERROR) - fprintf(stderr, "OpenGL Error %i\n",glGetError()); + if (REPORTGLERROR("checking framebuffer status")) return false; if (status == GL_FRAMEBUFFER_COMPLETE) result = true; else if (status == GL_FRAMEBUFFER_UNSUPPORTED) - fprintf(stderr, "GL_FRAMEBUFFER_UNSUPPORTED\n"); + cerr << "GL_FRAMEBUFFER_UNSUPPORTED\n"; else if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) - fprintf(stderr, "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT\n"); + cerr << "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT\n"; else if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) - fprintf(stderr, "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT\n"); + cerr << "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT\n"; else if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT) - fprintf(stderr, "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT\n"); + cerr << "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT\n"; else if (status == GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT) - fprintf(stderr, "GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT\n"); + cerr << "GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT\n"; else if (status == GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT) - fprintf(stderr, "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT\n"); + cerr << "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT\n"; else if (status == GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT) - fprintf(stderr, "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT\n"); + cerr << "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT\n"; else if (status == GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT) - fprintf(stderr, "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT\n"); + cerr << "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT\n"; else - fprintf(stderr, "Unknown Code: glCheckFramebufferStatusEXT returned %i\n",status); + cerr << "Unknown Code: glCheckFramebufferStatusEXT returned %i\n",status; return result; } @@ -69,7 +77,7 @@ bool fbo_ext_init(fbo_t *fbo, size_t width, size_t height) // Generate and bind FBO glGenFramebuffersEXT(1, &fbo->fbo_id); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->fbo_id); - REPORTGLERROR("binding framebuffer"); + if (REPORTGLERROR("binding framebuffer")) return false; // Generate depth and render buffers glGenRenderbuffersEXT(1, &fbo->depthbuf_id); @@ -81,20 +89,20 @@ bool fbo_ext_init(fbo_t *fbo, size_t width, size_t height) // Attach render and depth buffers glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, fbo->renderbuf_id); - REPORTGLERROR("specifying color render buffer"); + if (REPORTGLERROR("specifying color render buffer")) return false; if (!check_fbo_status()) { - fprintf(stderr, "Problem with OpenGL framebuffer after specifying color render buffer.\n"); + cerr << "Problem with OpenGL framebuffer after specifying color render buffer.\n"; return false; } glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo->depthbuf_id); - REPORTGLERROR("specifying depth render buffer"); + if (REPORTGLERROR("specifying depth render buffer")) return false; if (!check_fbo_status()) { - fprintf(stderr, "Problem with OpenGL framebuffer after specifying depth render buffer.\n"); + cerr << "Problem with OpenGL framebuffer after specifying depth render buffer.\n"; return false; } @@ -106,7 +114,7 @@ bool fbo_arb_init(fbo_t *fbo, size_t width, size_t height) // Generate and bind FBO glGenFramebuffers(1, &fbo->fbo_id); glBindFramebuffer(GL_FRAMEBUFFER, fbo->fbo_id); - REPORTGLERROR("binding framebuffer"); + if (REPORTGLERROR("binding framebuffer")) return false; // Generate depth and render buffers glGenRenderbuffers(1, &fbo->depthbuf_id); @@ -118,19 +126,19 @@ bool fbo_arb_init(fbo_t *fbo, size_t width, size_t height) // Attach render and depth buffers glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fbo->renderbuf_id); - REPORTGLERROR("specifying color render buffer"); + if (REPORTGLERROR("specifying color render buffer")) return false; - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - fprintf(stderr, "Problem with OpenGL framebuffer after specifying color render buffer.\n"); + if (!check_fbo_status()) { + cerr << "Problem with OpenGL framebuffer after specifying color render buffer.\n"; return false; } glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fbo->depthbuf_id); - REPORTGLERROR("specifying depth render buffer"); + if (REPORTGLERROR("specifying depth render buffer")) return false; - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - fprintf(stderr, "Problem with OpenGL framebuffer after specifying depth render buffer.\n"); + if (!check_fbo_status()) { + cerr << "Problem with OpenGL framebuffer after specifying depth render buffer.\n"; return false; } @@ -156,7 +164,7 @@ bool fbo_init(fbo_t *fbo, size_t width, size_t height) else if (use_ext()) result = fbo_ext_init(fbo, width, height); else - fprintf(stderr, "Framebuffer Object extension not found by GLEW\n"); + cerr << "Framebuffer Object extension not found by GLEW\n"; return result; } @@ -165,19 +173,19 @@ bool fbo_resize(fbo_t *fbo, size_t width, size_t height) if (use_ext()) { glBindRenderbufferEXT(GL_RENDERBUFFER, fbo->depthbuf_id); glRenderbufferStorageEXT(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); - REPORTGLERROR("creating depth render buffer"); + if (REPORTGLERROR("creating depth render buffer")) return false; glBindRenderbufferEXT(GL_RENDERBUFFER, fbo->renderbuf_id); glRenderbufferStorageEXT(GL_RENDERBUFFER, GL_RGBA8, width, height); - REPORTGLERROR("creating color render buffer"); + if (REPORTGLERROR("creating color render buffer")) return false; } else { glBindRenderbuffer(GL_RENDERBUFFER, fbo->depthbuf_id); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); - REPORTGLERROR("creating depth render buffer"); + if (REPORTGLERROR("creating depth render buffer")) return false; glBindRenderbuffer(GL_RENDERBUFFER, fbo->renderbuf_id); glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height); - REPORTGLERROR("creating color render buffer"); + if (REPORTGLERROR("creating color render buffer")) return false; } return true; From 84b90972f48a0733772fc1217e5598dd8f4b399d Mon Sep 17 00:00:00 2001 From: Don Bright Date: Sun, 9 Oct 2011 22:29:12 -0500 Subject: [PATCH 23/93] fix glx initialization, better error handling, better portability --- tests/OffscreenContext.cc | 203 +++++++++++++++++++++++--------------- tests/fbo.h | 2 + 2 files changed, 124 insertions(+), 81 deletions(-) diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc index f1046ba6..aa52705c 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContext.cc @@ -24,103 +24,145 @@ using namespace std; struct OffscreenContext { GLXContext openGLContext; - Display *xDisplay; - GLXPixmap glx_pixmap; - Pixmap x11_pixmap; + Display *xdisplay; + Window xwindow; int width; int height; fbo_t *fbo; }; -Bool glx_1_3_pixmap_dummy_context(OffscreenContext *ctx, Bool hybrid) +void offscreen_context_init(OffscreenContext &ctx, int width, int height) { - XVisualInfo *vInfo; - GLXFBConfig *fbConfigs; + ctx.width = width; + ctx.height = height; + ctx.openGLContext = NULL; + ctx.xdisplay = NULL; + ctx.xwindow = NULL; + ctx.fbo = NULL; +} - int numReturned; - int result; - int dummyAttributes_1_3[] = { - GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT, +static XErrorHandler original_xlib_handler = (XErrorHandler) NULL; +static bool XCreateWindow_failed = false; +static int XCreateWindow_error(Display *dpy, XErrorEvent *event) +{ + cerr << "XCreateWindow failed: XID: " << event->resourceid + << " request: " << (int)event->request_code + << " minor: " << (int)event->minor_code << "\n"; + char description[1024]; + XGetErrorText( dpy, event->error_code, description, 1023 ); + cerr << " error message: " << description << "\n"; + XCreateWindow_failed = true; + return 0; +} + +bool create_glx_dummy_window(OffscreenContext &ctx) +{ + /* + create a dummy X window without showing it. (without 'mapping' it) + save information to the ctx + + based on http://www.opengl.org/sdk/docs/man/xhtml/glXIntro.xml + which was originally Copyright © 1991-2006 Silicon Graphics, Inc. + licensed under the SGI Free Software B License. + See http://oss.sgi.com/projects/FreeB/. + + also based on glxgears.c by Brian Paul from mesa-demos (mesa3d.org) + + purposely does not use glxCreateWindow, to avoid Mesa warnings. + + this will alter ctx.openGLContext and ctx.xwindow if successfull + */ + + int attributes[] = { + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, GLX_RENDER_TYPE, GLX_RGBA_BIT, + GLX_RED_SIZE, 1, + GLX_GREEN_SIZE, 1, + GLX_BLUE_SIZE, 1, None }; - fbConfigs = glXChooseFBConfig( ctx->xDisplay, - DefaultScreen(ctx->xDisplay), - dummyAttributes_1_3, &numReturned ); - if ( fbConfigs == NULL ) { - cerr << "GLX error: glXChooseFBConfig failed"; - return False; + Display *dpy = ctx.xdisplay; + + int numReturned = 0; + GLXFBConfig *fbconfigs = glXChooseFBConfig( dpy, DefaultScreen(dpy), attributes, &numReturned ); + if ( fbconfigs == NULL ) { + cerr << "glXChooseFBConfig failed\n"; + return false; } - vInfo = glXGetVisualFromFBConfig( ctx->xDisplay, fbConfigs[0] ); - if ( vInfo == NULL ) { - cerr << "GLX error: glXGetVisualFromFBConfig failed"; - return False; + XVisualInfo *visinfo = glXGetVisualFromFBConfig( dpy, fbconfigs[0] ); + if ( visinfo == NULL ) { + cerr << "glXGetVisualFromFBConfig failed\n"; + XFree( fbconfigs ); + return false; } - ctx->x11_pixmap = XCreatePixmap( ctx->xDisplay, DefaultRootWindow(ctx->xDisplay) , 10, 10, 8 ); + original_xlib_handler = XSetErrorHandler( XCreateWindow_error ); + Window xWin = XCreateSimpleWindow( dpy, DefaultRootWindow(dpy), 0,0,10,10, 0,0,0 ); + // can't depend on xWin==NULL at failure. catch Xlib Errors instead. + XSync( dpy, false ); + if ( XCreateWindow_failed ) { + XFree( visinfo ); + XFree( fbconfigs ); + return false; + } + XSetErrorHandler( original_xlib_handler ); + // do not call XMapWindow - keep the window hidden - if (hybrid) { - // GLX 1.2 - prevent Mesa warning - ctx->glx_pixmap = glXCreateGLXPixmap( ctx->xDisplay, vInfo, ctx->x11_pixmap ); - } else { - // GLX 1.3 - ctx->glx_pixmap = glXCreatePixmap( ctx->xDisplay, fbConfigs[0], ctx->x11_pixmap, NULL ); // GLX 1.3 + GLXContext context = glXCreateNewContext( dpy, fbconfigs[0], GLX_RGBA_TYPE, NULL, True ); + if ( context == NULL ) { + cerr << "glXGetVisualFromFBConfig failed\n"; + XDestroyWindow( dpy, xWin ); + XFree( visinfo ); + XFree( fbconfigs ); + return false; + } + + if (!glXMakeContextCurrent( dpy, xWin, xWin, context )) { + cerr << "glXMakeContextCurrent failed\n"; + XDestroyWindow( dpy, xWin ); + glXDestroyContext( dpy, context ); + XFree( visinfo ); + XFree( fbconfigs ); + return false; } - ctx->openGLContext = glXCreateNewContext( ctx->xDisplay, fbConfigs[0], GLX_RGBA_TYPE, NULL, True ); - if ( ctx->openGLContext == NULL ) { - cerr << "GLX error: glXCreateNewContext failed"; - return False; - } + ctx.openGLContext = context; + ctx.xwindow = xWin; - result = glXMakeContextCurrent( ctx->xDisplay, ctx->glx_pixmap, ctx->glx_pixmap, ctx->openGLContext ); - if ( result == False ) { - cerr << "GLX error: glXMakeContextCurrent failed"; - return False; - } + XFree( visinfo ); + XFree( fbconfigs ); - return True; + return true; } -Bool make_glx_dummy_context(OffscreenContext *ctx) + +Bool create_glx_dummy_context(OffscreenContext &ctx) { - /* - Before opening a framebuffer, an OpenGL context must be created. - For GLX, you can do this by creating a 'Pixmap' drawable then - creating the Context off of that. The Pixmap is then never used. - */ + // This will alter ctx.openGLContext and ctx.xdisplay and ctx.xwindow if successfull int major; int minor; + Bool result = False; - ctx->xDisplay = XOpenDisplay( NULL ); - if ( ctx->xDisplay == NULL ) { + ctx.xdisplay = XOpenDisplay( NULL ); + if ( ctx.xdisplay == NULL ) { cerr << "Unable to open a connection to the X server\n"; return False; } - /* - On some systems, the GLX library will report that it is version - 1.2, but some 1.3 functions will be implemented, and, furthermore, - some 1.2 functions won't work right, while the 1.3 functions will, - but glXCreatePixmp will still generate a MESA GLX 1.3 runtime warning. + glXQueryVersion(ctx.xdisplay, &major, &minor); - To workaround this, detect the situation and use 'hybrid' mix of - 1.2 and 1.3 as needed. - */ - glXQueryVersion(ctx->xDisplay, &major, &minor); - - if (major==1 && minor<=2) { - if (glXCreatePixmap!=NULL) { // 1.3 function exists, even though its 1.2 - return glx_1_3_pixmap_dummy_context(ctx,True); - } else { - cerr << "OpenGL error: GLX version 1.3 functions missing. Your GLX: " << major << "." << minor << endl; - return False; - } - } else if (major>=1 && minor>=3) { - return glx_1_3_pixmap_dummy_context(ctx,False); + if ( major==1 && minor<=2 && glXGetVisualFromFBConfig==NULL ) { + cerr << "Error: GLX version 1.3 functions missing. " + << "Your GLX version: " << major << "." << minor << endl; + XCloseDisplay( ctx.xdisplay ); + } else { + // if glXGetVisualFromFBConfig exists, pretend we have >=1.3 + result = create_glx_dummy_window(ctx); } + + return result; } void glewCheck() { @@ -145,16 +187,11 @@ void glewCheck() { OffscreenContext *create_offscreen_context(int w, int h) { OffscreenContext *ctx = new OffscreenContext; - ctx->width = w; - ctx->height = h; - ctx->openGLContext = NULL; - ctx->xDisplay = NULL; - ctx->glx_pixmap = NULL; - ctx->x11_pixmap = NULL; - ctx->fbo = NULL; + offscreen_context_init( *ctx, w, h ); - // fill ctx->xDisplay, ctx->openGLContext, x11_pixmap, glx_pixmap - if (!make_glx_dummy_context(ctx)) { + // before an FBO can be setup, a GLX context must be created + // this call alters ctx->xDisplay and ctx->openGLContext + if (!create_glx_dummy_context( *ctx )) { return NULL; } @@ -171,12 +208,15 @@ OffscreenContext *create_offscreen_context(int w, int h) bool teardown_offscreen_context(OffscreenContext *ctx) { - fbo_unbind(ctx->fbo); - fbo_delete(ctx->fbo); - glXDestroyPixmap(ctx->xDisplay, ctx->glx_pixmap ); - XFreePixmap(ctx->xDisplay, ctx->x11_pixmap ); - glXDestroyContext( ctx->xDisplay, ctx->openGLContext ); - return true; + if (ctx) { + fbo_unbind(ctx->fbo); + fbo_delete(ctx->fbo); + XDestroyWindow( ctx->xdisplay, ctx->xwindow ); + glXDestroyContext( ctx->xdisplay, ctx->openGLContext ); + XCloseDisplay( ctx->xdisplay ); + return true; + } + return false; } /*! @@ -184,6 +224,7 @@ bool teardown_offscreen_context(OffscreenContext *ctx) */ bool save_framebuffer(OffscreenContext *ctx, const char *filename) { + if (!ctx || !filename) return false; int samplesPerPixel = 4; // R, G, B and A GLubyte pixels[ctx->width * ctx->height * samplesPerPixel]; glReadPixels(0, 0, ctx->width, ctx->height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); @@ -206,5 +247,5 @@ bool save_framebuffer(OffscreenContext *ctx, const char *filename) void bind_offscreen_context(OffscreenContext *ctx) { - fbo_bind(ctx->fbo); + if (ctx) fbo_bind(ctx->fbo); } diff --git a/tests/fbo.h b/tests/fbo.h index 1ee90074..943862f1 100644 --- a/tests/fbo.h +++ b/tests/fbo.h @@ -20,4 +20,6 @@ void fbo_delete(fbo_t *fbo); GLuint fbo_bind(fbo_t *fbo); void fbo_unbind(fbo_t *fbo); +bool REPORTGLERROR(const char * task); + #endif From 67e3c307bbf3b26e6d8fa7f276c55c45095d772d Mon Sep 17 00:00:00 2001 From: Don Bright Date: Sun, 9 Oct 2011 22:34:24 -0500 Subject: [PATCH 24/93] documentation --- tests/OffscreenContext.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc index aa52705c..9d6fe7bf 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContext.cc @@ -190,7 +190,8 @@ OffscreenContext *create_offscreen_context(int w, int h) offscreen_context_init( *ctx, w, h ); // before an FBO can be setup, a GLX context must be created - // this call alters ctx->xDisplay and ctx->openGLContext + // this call alters ctx->xDisplay and ctx->openGLContext + // and ctx->xwindow if successfull if (!create_glx_dummy_context( *ctx )) { return NULL; } From b2f193d631c2fbbab4536fab8c209d286eef5923 Mon Sep 17 00:00:00 2001 From: Don Bright Date: Sun, 9 Oct 2011 23:18:10 -0500 Subject: [PATCH 25/93] more documentation and tab removal --- tests/OffscreenContext.cc | 214 +++++++++++++++++++------------------- 1 file changed, 108 insertions(+), 106 deletions(-) diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc index 9d6fe7bf..4b665011 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContext.cc @@ -25,7 +25,7 @@ struct OffscreenContext { GLXContext openGLContext; Display *xdisplay; - Window xwindow; + Window xwindow; int width; int height; fbo_t *fbo; @@ -37,7 +37,7 @@ void offscreen_context_init(OffscreenContext &ctx, int width, int height) ctx.height = height; ctx.openGLContext = NULL; ctx.xdisplay = NULL; - ctx.xwindow = NULL; + ctx.xwindow = NULL; ctx.fbo = NULL; } @@ -45,124 +45,126 @@ static XErrorHandler original_xlib_handler = (XErrorHandler) NULL; static bool XCreateWindow_failed = false; static int XCreateWindow_error(Display *dpy, XErrorEvent *event) { - cerr << "XCreateWindow failed: XID: " << event->resourceid - << " request: " << (int)event->request_code - << " minor: " << (int)event->minor_code << "\n"; - char description[1024]; - XGetErrorText( dpy, event->error_code, description, 1023 ); - cerr << " error message: " << description << "\n"; - XCreateWindow_failed = true; - return 0; + cerr << "XCreateWindow failed: XID: " << event->resourceid + << " request: " << (int)event->request_code + << " minor: " << (int)event->minor_code << "\n"; + char description[1024]; + XGetErrorText( dpy, event->error_code, description, 1023 ); + cerr << " error message: " << description << "\n"; + XCreateWindow_failed = true; + return 0; } bool create_glx_dummy_window(OffscreenContext &ctx) { - /* - create a dummy X window without showing it. (without 'mapping' it) - save information to the ctx + /* + create a dummy X window without showing it. (without 'mapping' it) + save information to the ctx - based on http://www.opengl.org/sdk/docs/man/xhtml/glXIntro.xml - which was originally Copyright © 1991-2006 Silicon Graphics, Inc. - licensed under the SGI Free Software B License. - See http://oss.sgi.com/projects/FreeB/. + based on http://www.opengl.org/sdk/docs/man/xhtml/glXIntro.xml + which was originally Copyright © 1991-2006 Silicon Graphics, Inc. + licensed under the SGI Free Software B License. + See http://oss.sgi.com/projects/FreeB/. - also based on glxgears.c by Brian Paul from mesa-demos (mesa3d.org) + also based on glxgears.c by Brian Paul from mesa-demos (mesa3d.org) - purposely does not use glxCreateWindow, to avoid Mesa warnings. + purposely does not use glxCreateWindow, to avoid crashes, + "failed to create drawable" errors, and Mesa "This is an application bug!" + warnings about GLX 1.3. - this will alter ctx.openGLContext and ctx.xwindow if successfull - */ + this function will alter ctx.openGLContext and ctx.xwindow if successfull + */ - int attributes[] = { - GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, - GLX_RENDER_TYPE, GLX_RGBA_BIT, - GLX_RED_SIZE, 1, - GLX_GREEN_SIZE, 1, - GLX_BLUE_SIZE, 1, - None - }; + int attributes[] = { + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_RENDER_TYPE, GLX_RGBA_BIT, + GLX_RED_SIZE, 1, + GLX_GREEN_SIZE, 1, + GLX_BLUE_SIZE, 1, + None + }; - Display *dpy = ctx.xdisplay; + Display *dpy = ctx.xdisplay; - int numReturned = 0; - GLXFBConfig *fbconfigs = glXChooseFBConfig( dpy, DefaultScreen(dpy), attributes, &numReturned ); - if ( fbconfigs == NULL ) { - cerr << "glXChooseFBConfig failed\n"; - return false; - } + int numReturned = 0; + GLXFBConfig *fbconfigs = glXChooseFBConfig( dpy, DefaultScreen(dpy), attributes, &numReturned ); + if ( fbconfigs == NULL ) { + cerr << "glXChooseFBConfig failed\n"; + return false; + } - XVisualInfo *visinfo = glXGetVisualFromFBConfig( dpy, fbconfigs[0] ); - if ( visinfo == NULL ) { - cerr << "glXGetVisualFromFBConfig failed\n"; - XFree( fbconfigs ); - return false; - } + XVisualInfo *visinfo = glXGetVisualFromFBConfig( dpy, fbconfigs[0] ); + if ( visinfo == NULL ) { + cerr << "glXGetVisualFromFBConfig failed\n"; + XFree( fbconfigs ); + return false; + } - original_xlib_handler = XSetErrorHandler( XCreateWindow_error ); - Window xWin = XCreateSimpleWindow( dpy, DefaultRootWindow(dpy), 0,0,10,10, 0,0,0 ); - // can't depend on xWin==NULL at failure. catch Xlib Errors instead. - XSync( dpy, false ); - if ( XCreateWindow_failed ) { - XFree( visinfo ); - XFree( fbconfigs ); - return false; - } - XSetErrorHandler( original_xlib_handler ); - // do not call XMapWindow - keep the window hidden + original_xlib_handler = XSetErrorHandler( XCreateWindow_error ); + Window xWin = XCreateSimpleWindow( dpy, DefaultRootWindow(dpy), 0,0,10,10, 0,0,0 ); + // can't depend on xWin==NULL at failure. catch Xlib Errors instead. + XSync( dpy, false ); + if ( XCreateWindow_failed ) { + XFree( visinfo ); + XFree( fbconfigs ); + return false; + } + XSetErrorHandler( original_xlib_handler ); + // do not call XMapWindow - keep the window hidden - GLXContext context = glXCreateNewContext( dpy, fbconfigs[0], GLX_RGBA_TYPE, NULL, True ); - if ( context == NULL ) { - cerr << "glXGetVisualFromFBConfig failed\n"; - XDestroyWindow( dpy, xWin ); - XFree( visinfo ); - XFree( fbconfigs ); - return false; - } + GLXContext context = glXCreateNewContext( dpy, fbconfigs[0], GLX_RGBA_TYPE, NULL, True ); + if ( context == NULL ) { + cerr << "glXGetVisualFromFBConfig failed\n"; + XDestroyWindow( dpy, xWin ); + XFree( visinfo ); + XFree( fbconfigs ); + return false; + } - if (!glXMakeContextCurrent( dpy, xWin, xWin, context )) { - cerr << "glXMakeContextCurrent failed\n"; - XDestroyWindow( dpy, xWin ); - glXDestroyContext( dpy, context ); - XFree( visinfo ); - XFree( fbconfigs ); - return false; - } + if (!glXMakeContextCurrent( dpy, xWin, xWin, context )) { + cerr << "glXMakeContextCurrent failed\n"; + XDestroyWindow( dpy, xWin ); + glXDestroyContext( dpy, context ); + XFree( visinfo ); + XFree( fbconfigs ); + return false; + } ctx.openGLContext = context; - ctx.xwindow = xWin; + ctx.xwindow = xWin; - XFree( visinfo ); - XFree( fbconfigs ); + XFree( visinfo ); + XFree( fbconfigs ); - return true; + return true; } Bool create_glx_dummy_context(OffscreenContext &ctx) { - // This will alter ctx.openGLContext and ctx.xdisplay and ctx.xwindow if successfull - int major; - int minor; - Bool result = False; + // This will alter ctx.openGLContext and ctx.xdisplay and ctx.xwindow if successfull + int major; + int minor; + Bool result = False; - ctx.xdisplay = XOpenDisplay( NULL ); - if ( ctx.xdisplay == NULL ) { - cerr << "Unable to open a connection to the X server\n"; - return False; - } + ctx.xdisplay = XOpenDisplay( NULL ); + if ( ctx.xdisplay == NULL ) { + cerr << "Unable to open a connection to the X server\n"; + return False; + } - glXQueryVersion(ctx.xdisplay, &major, &minor); + glXQueryVersion(ctx.xdisplay, &major, &minor); - if ( major==1 && minor<=2 && glXGetVisualFromFBConfig==NULL ) { - cerr << "Error: GLX version 1.3 functions missing. " - << "Your GLX version: " << major << "." << minor << endl; - XCloseDisplay( ctx.xdisplay ); - } else { - // if glXGetVisualFromFBConfig exists, pretend we have >=1.3 - result = create_glx_dummy_window(ctx); - } + if ( major==1 && minor<=2 && glXGetVisualFromFBConfig==NULL ) { + cerr << "Error: GLX version 1.3 functions missing. " + << "Your GLX version: " << major << "." << minor << endl; + } else { + // if glXGetVisualFromFBConfig exists, pretend we have >=1.3 + result = create_glx_dummy_window(ctx); + } - return result; + if (!result) XCloseDisplay( ctx.xdisplay ); + return result; } void glewCheck() { @@ -187,11 +189,11 @@ void glewCheck() { OffscreenContext *create_offscreen_context(int w, int h) { OffscreenContext *ctx = new OffscreenContext; - offscreen_context_init( *ctx, w, h ); + offscreen_context_init( *ctx, w, h ); - // before an FBO can be setup, a GLX context must be created + // before an FBO can be setup, a GLX context must be created // this call alters ctx->xDisplay and ctx->openGLContext - // and ctx->xwindow if successfull + // and ctx->xwindow if successfull if (!create_glx_dummy_context( *ctx )) { return NULL; } @@ -209,15 +211,15 @@ OffscreenContext *create_offscreen_context(int w, int h) bool teardown_offscreen_context(OffscreenContext *ctx) { - if (ctx) { - fbo_unbind(ctx->fbo); - fbo_delete(ctx->fbo); - XDestroyWindow( ctx->xdisplay, ctx->xwindow ); - glXDestroyContext( ctx->xdisplay, ctx->openGLContext ); - XCloseDisplay( ctx->xdisplay ); - return true; - } - return false; + if (ctx) { + fbo_unbind(ctx->fbo); + fbo_delete(ctx->fbo); + XDestroyWindow( ctx->xdisplay, ctx->xwindow ); + glXDestroyContext( ctx->xdisplay, ctx->openGLContext ); + XCloseDisplay( ctx->xdisplay ); + return true; + } + return false; } /*! @@ -225,7 +227,7 @@ bool teardown_offscreen_context(OffscreenContext *ctx) */ bool save_framebuffer(OffscreenContext *ctx, const char *filename) { - if (!ctx || !filename) return false; + if (!ctx || !filename) return false; int samplesPerPixel = 4; // R, G, B and A GLubyte pixels[ctx->width * ctx->height * samplesPerPixel]; glReadPixels(0, 0, ctx->width, ctx->height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); @@ -248,5 +250,5 @@ bool save_framebuffer(OffscreenContext *ctx, const char *filename) void bind_offscreen_context(OffscreenContext *ctx) { - if (ctx) fbo_bind(ctx->fbo); + if (ctx) fbo_bind(ctx->fbo); } From 33da91d87d9bd36138ebde84d5c1db0606c9c1c0 Mon Sep 17 00:00:00 2001 From: don bright Date: Mon, 10 Oct 2011 00:45:53 -0400 Subject: [PATCH 26/93] make yee_compare downsample images, reduce false-negatives and increase speed --- tests/test_cmdline_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cmdline_tool.py b/tests/test_cmdline_tool.py index 7d7f23c6..3ac41dd2 100755 --- a/tests/test_cmdline_tool.py +++ b/tests/test_cmdline_tool.py @@ -117,7 +117,7 @@ def compare_png(resultfilename): print >> sys.stderr, "Error: OpenSCAD did not generate an image" return False print >> sys.stderr, 'Yee image compare: ', expectedfilename, ' ', resultfilename - if execute_and_redirect("./yee_compare", [expectedfilename, resultfilename], sys.stderr) != 0: + if execute_and_redirect("./yee_compare", [expectedfilename, resultfilename, "-downsample", "2"], sys.stderr) != 0: append_html_output(expectedfilename, resultfilename) return False return True From a010b7b74702ee2499bf1a2399d7792d3ec6c3b6 Mon Sep 17 00:00:00 2001 From: Don Bright Date: Sun, 9 Oct 2011 23:47:42 -0500 Subject: [PATCH 27/93] reorder destruction of window to proper position --- tests/OffscreenContext.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc index 4b665011..9feaecc9 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContext.cc @@ -123,8 +123,8 @@ bool create_glx_dummy_window(OffscreenContext &ctx) if (!glXMakeContextCurrent( dpy, xWin, xWin, context )) { cerr << "glXMakeContextCurrent failed\n"; - XDestroyWindow( dpy, xWin ); glXDestroyContext( dpy, context ); + XDestroyWindow( dpy, xWin ); XFree( visinfo ); XFree( fbconfigs ); return false; From 6251fc326842b657feb4926c9a4fbb51f152248c Mon Sep 17 00:00:00 2001 From: notroot Date: Mon, 10 Oct 2011 13:40:37 +0000 Subject: [PATCH 28/93] +throwntogethertest expected images, made by Mesa software renderer in VMware --- tests/OffscreenContext.cc | 2 +- .../throwntogethertest/2d-3d-expected.png | Bin 0 -> 3775 bytes .../throwntogethertest/arc-expected.png | Bin 0 -> 2758 bytes .../assign-tests-expected.png | Bin 0 -> 5256 bytes .../background-modifier-expected.png | Bin 0 -> 15657 bytes .../throwntogethertest/child-tests-expected.png | Bin 0 -> 9234 bytes .../circle-advanced-expected.png | Bin 0 -> 5858 bytes .../circle-double-expected.png | Bin 0 -> 3961 bytes .../throwntogethertest/circle-expected.png | Bin 0 -> 2837 bytes .../circle-small-expected.png | Bin 0 -> 3838 bytes .../circle-tests-expected.png | Bin 0 -> 5265 bytes .../throwntogethertest/color-tests-expected.png | Bin 0 -> 9139 bytes .../throwntogethertest/cube-tests-expected.png | Bin 0 -> 3638 bytes .../cylinder-tests-expected.png | Bin 0 -> 8670 bytes .../difference-tests-expected.png | Bin 0 -> 7812 bytes .../disable-modifier-expected.png | Bin 0 -> 2954 bytes .../throwntogethertest/ellipse-arc-expected.png | Bin 0 -> 2227 bytes .../ellipse-arc-rot-expected.png | Bin 0 -> 3719 bytes .../throwntogethertest/ellipse-expected.png | Bin 0 -> 2836 bytes .../ellipse-reverse-expected.png | Bin 0 -> 3007 bytes .../throwntogethertest/ellipse-rot-expected.png | Bin 0 -> 3751 bytes .../for-nested-tests-expected.png | Bin 0 -> 21492 bytes .../throwntogethertest/for-tests-expected.png | Bin 0 -> 6927 bytes .../highlight-modifier-expected.png | Bin 0 -> 12885 bytes .../throwntogethertest/hull2-tests-expected.png | Bin 0 -> 4151 bytes .../ifelse-tests-expected.png | Bin 0 -> 4999 bytes .../import_dxf-tests-expected.png | Bin 0 -> 4414 bytes .../include-tests-expected.png | Bin 0 -> 8317 bytes .../intersection-tests-expected.png | Bin 0 -> 8006 bytes .../intersection_for-tests-expected.png | Bin 0 -> 5947 bytes .../linear_extrude-tests-expected.png | Bin 0 -> 8648 bytes .../lwpolyline-closed-expected.png | Bin 0 -> 2424 bytes .../throwntogethertest/lwpolyline-expected.png | Bin 0 -> 2424 bytes .../throwntogethertest/lwpolyline2-expected.png | Bin 0 -> 3279 bytes .../minkowski2-tests-expected.png | Bin 0 -> 3635 bytes .../minkowski3-tests-expected.png | Bin 0 -> 4455 bytes .../multiple-layers-expected.png | Bin 0 -> 3959 bytes .../polygon-concave-expected.png | Bin 0 -> 4527 bytes .../polygon-concave-hole-expected.png | Bin 0 -> 4136 bytes .../polygon-concave-simple-expected.png | Bin 0 -> 2645 bytes .../polygon-holes-touch-expected.png | Bin 0 -> 4147 bytes .../polygon-intersect-expected.png | Bin 0 -> 1810 bytes .../polygon-many-holes-expected.png | Bin 0 -> 4854 bytes .../polygon-mesh-expected.png | Bin 0 -> 1810 bytes .../polygon-overlap-expected.png | Bin 0 -> 1810 bytes .../polygon-riser-expected.png | Bin 0 -> 2883 bytes .../polygon-self-intersect-expected.png | Bin 0 -> 1810 bytes .../polygon-tests-expected.png | Bin 0 -> 4769 bytes .../throwntogethertest/polygon8-expected.png | Bin 0 -> 3546 bytes .../throwntogethertest/polygons-expected.png | Bin 0 -> 3239 bytes .../polyhedron-tests-expected.png | Bin 0 -> 6905 bytes .../projection-tests-expected.png | Bin 0 -> 5062 bytes .../render-tests-expected.png | Bin 0 -> 5629 bytes .../root-modifier-expected.png | Bin 0 -> 2954 bytes .../rotate_extrude-tests-expected.png | Bin 0 -> 6429 bytes .../sphere-tests-expected.png | Bin 0 -> 13794 bytes .../square-tests-expected.png | Bin 0 -> 3962 bytes .../surface-tests-expected.png | Bin 0 -> 35964 bytes .../transform-insert-expected.png | Bin 0 -> 3895 bytes .../transform-tests-expected.png | Bin 0 -> 14734 bytes .../triangle-with-duplicate-vertex-expected.png | Bin 0 -> 2594 bytes .../throwntogethertest/union-tests-expected.png | Bin 0 -> 5815 bytes 62 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 tests/regression/throwntogethertest/2d-3d-expected.png create mode 100644 tests/regression/throwntogethertest/arc-expected.png create mode 100644 tests/regression/throwntogethertest/assign-tests-expected.png create mode 100644 tests/regression/throwntogethertest/background-modifier-expected.png create mode 100644 tests/regression/throwntogethertest/child-tests-expected.png create mode 100644 tests/regression/throwntogethertest/circle-advanced-expected.png create mode 100644 tests/regression/throwntogethertest/circle-double-expected.png create mode 100644 tests/regression/throwntogethertest/circle-expected.png create mode 100644 tests/regression/throwntogethertest/circle-small-expected.png create mode 100644 tests/regression/throwntogethertest/circle-tests-expected.png create mode 100644 tests/regression/throwntogethertest/color-tests-expected.png create mode 100644 tests/regression/throwntogethertest/cube-tests-expected.png create mode 100644 tests/regression/throwntogethertest/cylinder-tests-expected.png create mode 100644 tests/regression/throwntogethertest/difference-tests-expected.png create mode 100644 tests/regression/throwntogethertest/disable-modifier-expected.png create mode 100644 tests/regression/throwntogethertest/ellipse-arc-expected.png create mode 100644 tests/regression/throwntogethertest/ellipse-arc-rot-expected.png create mode 100644 tests/regression/throwntogethertest/ellipse-expected.png create mode 100644 tests/regression/throwntogethertest/ellipse-reverse-expected.png create mode 100644 tests/regression/throwntogethertest/ellipse-rot-expected.png create mode 100644 tests/regression/throwntogethertest/for-nested-tests-expected.png create mode 100644 tests/regression/throwntogethertest/for-tests-expected.png create mode 100644 tests/regression/throwntogethertest/highlight-modifier-expected.png create mode 100644 tests/regression/throwntogethertest/hull2-tests-expected.png create mode 100644 tests/regression/throwntogethertest/ifelse-tests-expected.png create mode 100644 tests/regression/throwntogethertest/import_dxf-tests-expected.png create mode 100644 tests/regression/throwntogethertest/include-tests-expected.png create mode 100644 tests/regression/throwntogethertest/intersection-tests-expected.png create mode 100644 tests/regression/throwntogethertest/intersection_for-tests-expected.png create mode 100644 tests/regression/throwntogethertest/linear_extrude-tests-expected.png create mode 100644 tests/regression/throwntogethertest/lwpolyline-closed-expected.png create mode 100644 tests/regression/throwntogethertest/lwpolyline-expected.png create mode 100644 tests/regression/throwntogethertest/lwpolyline2-expected.png create mode 100644 tests/regression/throwntogethertest/minkowski2-tests-expected.png create mode 100644 tests/regression/throwntogethertest/minkowski3-tests-expected.png create mode 100644 tests/regression/throwntogethertest/multiple-layers-expected.png create mode 100644 tests/regression/throwntogethertest/polygon-concave-expected.png create mode 100644 tests/regression/throwntogethertest/polygon-concave-hole-expected.png create mode 100644 tests/regression/throwntogethertest/polygon-concave-simple-expected.png create mode 100644 tests/regression/throwntogethertest/polygon-holes-touch-expected.png create mode 100644 tests/regression/throwntogethertest/polygon-intersect-expected.png create mode 100644 tests/regression/throwntogethertest/polygon-many-holes-expected.png create mode 100644 tests/regression/throwntogethertest/polygon-mesh-expected.png create mode 100644 tests/regression/throwntogethertest/polygon-overlap-expected.png create mode 100644 tests/regression/throwntogethertest/polygon-riser-expected.png create mode 100644 tests/regression/throwntogethertest/polygon-self-intersect-expected.png create mode 100644 tests/regression/throwntogethertest/polygon-tests-expected.png create mode 100644 tests/regression/throwntogethertest/polygon8-expected.png create mode 100644 tests/regression/throwntogethertest/polygons-expected.png create mode 100644 tests/regression/throwntogethertest/polyhedron-tests-expected.png create mode 100644 tests/regression/throwntogethertest/projection-tests-expected.png create mode 100644 tests/regression/throwntogethertest/render-tests-expected.png create mode 100644 tests/regression/throwntogethertest/root-modifier-expected.png create mode 100644 tests/regression/throwntogethertest/rotate_extrude-tests-expected.png create mode 100644 tests/regression/throwntogethertest/sphere-tests-expected.png create mode 100644 tests/regression/throwntogethertest/square-tests-expected.png create mode 100644 tests/regression/throwntogethertest/surface-tests-expected.png create mode 100644 tests/regression/throwntogethertest/transform-insert-expected.png create mode 100644 tests/regression/throwntogethertest/transform-tests-expected.png create mode 100644 tests/regression/throwntogethertest/triangle-with-duplicate-vertex-expected.png create mode 100644 tests/regression/throwntogethertest/union-tests-expected.png diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc index 9feaecc9..01ab9c4f 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContext.cc @@ -37,7 +37,7 @@ void offscreen_context_init(OffscreenContext &ctx, int width, int height) ctx.height = height; ctx.openGLContext = NULL; ctx.xdisplay = NULL; - ctx.xwindow = NULL; + ctx.xwindow = (Window)NULL; ctx.fbo = NULL; } diff --git a/tests/regression/throwntogethertest/2d-3d-expected.png b/tests/regression/throwntogethertest/2d-3d-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..48b3c49b88f13f382168e1bc00111c207141be57 GIT binary patch literal 3775 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 Q1?9kF-KMprZv1cm3-N$c^8f$< literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/arc-expected.png b/tests/regression/throwntogethertest/arc-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..878380658d239383ae221d01c1f33ae1c1a0f8f9 GIT binary patch literal 2758 zcmeHJYfuwc6h6BfmWQFB_#hTZz(mj@E-MOFfhdTtfcU_oh+QG*XdQ{8(;yVGO2t}1 zP()GG_-d$AP-dK#wy4?GQn9v%vex>bCDgVtqDVC;hA!z{tka+U*Z%R>IrE)!zVqF? z=ia+JQj%wRdyVu00Pi`o6BYvC$i=}!H2EXr>j1nq%t@HOI2U{HQ^YRi>QUP}ZdFcf zXXACRY`;)KC}+%*T#z19WzxZ!my#z>wsQ-)@ToCpVHysdG>DWqWSs`InCKVC&pt z7i_b6av`4tdM)lSv|{zW>d)NkAnhIN=2L-Tfw0r(T+^+XGzt>s(O>38qy@onc}vk; z2#ci-%JQjMqO*T&9F~!IqF`C7}%*#tk*B`+*yrFij`x`kc~;M^~G>s3;wMoNNb2lr!ho|rfcmJd;51XMBAk;MP7g?peAjN zQ~%hV2_aZ}8Ho&+RQI~j_%W^^xYF7fp>delz?$mCo71CSkkPRG*kqwGQC!rt)5*wxZjuktr>DwIo;R;8Q-v^ zu5@Q-&hsqSO6OY=g_@*<<-s7MIhDz*0j-)mu8G6Tm4wMPn3pSICEb$ce0B(ay{s;> z&w|{o(%LJI$3rnSsfk0UcQj4K@s>#vHl&$5gbKOw%hz&ah!dYj3)MZgH8?A+Dm*S> zJDa(3F}hUC_?wIkexPM&lQDfTteA&*D*WK*L=veV42ddYdxQ`$3(B`lKTx=94743Y z3}b>IAf8;&)*Ho_LCUbJtbKI=&ZT*$ zMH43D_viF@*PYwOxWe#f9cAY9hqh$~(lPKRKkd1#A7SpdUwU&y?*@}G39PT}yMl^! zFJuQLRbz;f8?ULvc*`aU+u?0Q&PfT#C?UZU7RkmCCOO9DfBF_$#uZ=CT9=uOO&(A& zRfSrsJYimR*g%G?EfN9f4yC(Q{QP&J{=+x2IY&h>)OPM5*X^gfuQ_tABc`PV<56B1 zrZ~Liq1n~ssOTy6EAM__ksIf0tsBK&!(x!8`K8eEI22`?^gcfF<(T5`c|Y-L4#L4# z1+V}I1;Ad9fg2m?WC2ow2VgOZgL(%5g8#nHRWFO4KWzWsM!edhM4!hcX-z%dU1NVE p2mpLH%}m>*$-OJ~M*yOYxWU9&9_;SH?*n z)JiX31?Fs&pjCua05EDRJ5siy0O8JmrT<#u|3-ni?IwsfGuI-#!Kst*dMmrC?vH-! z-A?IyCNJ4$P4`EE$68IwT?P;w_O$7{r8No#TE=N}DXEw@Tfoh^&!l`QC3T>i^E`b; zG28!r12XNZ4Z!Uj`b<)j4#t5Ex%+t3e=zW^s_&^RGZgEc>$2E-Z$1W|a$D{hH`m6% z%R7CUCue6r@45VKslBGRwRgTjB@#suzeQD;uN5eXLQyK9D@g>oYGD6-ZzPz+sH{Q0 z+O1(>O^w^P=P~WT6BU4wft8g)x#Q*QU@aV2@>d0uTUv)QyX64{4_0r2W%6dskL5AF zMCUhyk@8`1R>5-ZQ4+HD%{34O{KccQyFE2P9RYkZItAXUDO?z|@B|oe`KAr{9p8Jw z+=7-3M)m>%oD=flLbK&hsm)UzI%SHrzR?6mtU#ORW$0Z{)Y{ZdKaZTgjIKD5C*^is zYy!1i0xIC^j&`l|BjB7~Fb3#J0~i28;!kRt|B&2EqjGj%bjN{{^WSF=OB_M#M}*UM z@eMnDugFWp!we-_$&;+H?|-^L&U#J`+2}=4$ACN?ED~0-n#Vmuqr6~nQ$COL^6@oDI>Q+C zi`(nhjs<44CFi^I-OI+k^B)a5+InPzdWxWwwK{frY2D$thZJDkQiQprkk!dm1=s)C zfjt-mcWd~dSk-r^_Xji(w+hv+6480~VHnu1#7mberx1$)i$~Mw3WxAhX2U7~Pq?7; z2bD)8K~z9YV{r|SvVY!vXrz$tI-ck8cm4VE>ikh z&`_6V)QCYEUB)R?e%>Roc?bjN+HpjiXwK5LT#v+f&{gAaLF3CGL6tQfEhZOZjx*e} zJ+l+it9F~F)3wIlnKoH@nF`%guxt+c1);L@D{A)gy|WlgX{ZW z<&|T?m?WIZc%(nK>e zY-LA0f|4(vR#Ut-RQ0~Dw2$4t(0|M+JozubVg>KxC^pj^$eokS9j^*b{=4{oiIeoT z60eGR@sZB?A$v!V8Z11b<@>xGzy4r_q*1L}Ve2sze+=2h#W5j+I6L@dG+H`9y`wep zqpOP*cyT*^i-mN68fN0xkKJK$ zrF;afcHr>26&skQ4^gmFaSmZ*Jao0}C8g3C#UME^?2(AepT9nY*$kGCtYGKOW_m6o z1%TiT@el_5T`4rg02^BUtHZ@6TLEEj7<&d$)Lwfuj0DPHeUXwHxY zKY)tM0(WLv0RvY#^&WS7lfAFgFRnS)M3cG&%27k_ zTJa%bnDdmX`{HR$K9Z`6>gQe0BT0kXg!dlvl<>(NQXMU?j+ly+M^E7>Yp@?h9W(o} zsYgsZlb)Yl$Ssz-<05S%`Ho|a{`+3k5om)=or@Ll{c{cph`J&a%l;)-{Dr^d?|g?& zyAn=onSdTSCmiC6-BNsc80uLDvmB$}mEymsmMwSvoZ!7WupD6s?~||MAJz-4(^$MK zTYzwio=an>u`oAouY1`~jjf!@umc=h)< zol*SLZ_X|&h%j9?^81#>jn9hStZGkt)fv1E)zx||UCga=)V|&A-u8JY+fa0N9b7zE z4=Gal)6WytbVW$)bvZp)lVd;?APS=@tubXaR99)7e%D)WU!H?gc+LltlflpW3%vB^ z4G|kZ_M?{$_-ULBuB(S`cpn@(o%07c`qmjc)%3^#)%nyP)s>iJK;<)h)lCc?r1v;N zZM)H7Oex>L$gycGgT_>VzXvgB)Bj{u==|9%yxmD?Q7O#8}NTIB;PNrPC0QC=V|m`DLbHO&P~_5g<;z8!KS zfERVb157q^zWgpErvaX7edo0UqAd7uf}axS!Y6SeyKs~^Ws=9W$_7@cfha)so*Zo8 z`zS8Hb$)Ts?AIhWE`xcPobKzfZiO$MD`!}31%s)IN@1Y5FD0!`AH5*-yenPeHvoTF zB3hjhxMW^QJ&T?A%J;ps5&AyO;s(a~DiW=p?;={A7xbxR#OvA81V~Xg9wac**GPHMaL`$PGifw1*=%x+~oz5hsD3}tqBvfpN*t`D}vMu zI9a%?fYt~VKiF)C)y@)!+G4ML`hzOickjBy(*1SRv5_d;(RZkN7tciJfCEDN2JPk@ z*hYKiLW=Lkk+BlaV(XRk_~$&_V~*gjma%0P6>Hc!MtJaC)?PinAu8WDQQ zcc~R)aYCM`uUNIFIhML{)-S+gVU0eOf6^o6NKSM#F-Yyyemo^FuG2mc{<=28bi%V; zzY%>_-@)A#<${7D=2J;+lyHWpQ~1D*r1U)(^D!EJtWs)!k8BU|T>vGV&DpLKS?d?F z7yEJbqAWZ!f$d?wCANj|*XTqBA*R@*7NkdjZ;#Z9Jpu<$9h_A~bFr4TO3gERrQC0s zae>B+0kb;AEUyWiNfbKic}bGa)N||yIaQF|5u8hV{8b90LJCEn>GNT6(VOlWCurZH z4K}`{I3~;&a0KXqdnO4vJu^2XvRMeBHx^GDecR<4-r|@4O0vucyy92ly{H(qy4r~f}a@w&nV_}wmr1QM|KA6ED~mb`xTEx=OQjqMx-CN0)^a(1e;} zX%JruYO`$IM84b-9UafjrwIeC$>=h|*B><_%FRadY?ye;Jv07EgZDqS=%XkkqH!@4YcH|DhW&8rTzbd?n2rG|+n^O`%m0?Jj!xmLbd`P-^hkpTTVdeh0W+BeBbUy3RBca> zY}Zlr1_Soml`zNt18YNm>T{UF$i#=V2=*oAC&P=sArL&eoSIKMssYYMEzdSB4T;%( z>-DLE2dYNxqs5N&%1m*Zzhc)r4*X<~Avd3df#3XTFRAD1SLlFzQj`kFmx#9KzCDC~ zhDIhCp$H!mCz=07Vc)KZekeju9b(z%ctc26q*%`(J>M4#T{sdr)lS=ts?=SafJi4m>NBi6hfIRKN8Nxs*GtN*=HAdbPd9f z0$kmFV-$dZ3gM^i66C;&B=q9OM6Y-{8f(u<|6l-lL_Sr_Nz#LpXQHoG_OYKVMgQ8H74vcUc02~0fZ~$pf ze_Uhzf0i%{lKm*Di*LApy#vV2LC68u5Su`6ALObL=F&}se)#1A7WoFO57_N5 I+e=OO4;qY-uK)l5 literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/background-modifier-expected.png b/tests/regression/throwntogethertest/background-modifier-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..79810f56abb021e23c0598a2f751d0adb551ee86 GIT binary patch literal 15657 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 literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/circle-advanced-expected.png b/tests/regression/throwntogethertest/circle-advanced-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..f49cd3d53fec3a0af8231d59c09149d721d29814 GIT binary patch literal 5858 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 literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/circle-double-expected.png b/tests/regression/throwntogethertest/circle-double-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..185bdbe8490f0e8159edae3a2a9275a823ef9b29 GIT binary patch literal 3961 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 literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/circle-expected.png b/tests/regression/throwntogethertest/circle-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..c001955931971faddc21caee86922952b3f0f8c7 GIT binary patch literal 2837 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 O4_F*Ib-3D;5c^-1z^}Ui literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/color-tests-expected.png b/tests/regression/throwntogethertest/color-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..6b500805ceee76e9c6363a691c112da9bc1c137c GIT binary patch literal 9139 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 Q)qVpXkKNnLzvLwS7s8?nLjV8( literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/cylinder-tests-expected.png b/tests/regression/throwntogethertest/cylinder-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3ed33a693ebf2cf3d8f5987ec90553d38f3586 GIT binary patch literal 8670 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 cdHlqQ0}dy18~^|S literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/difference-tests-expected.png b/tests/regression/throwntogethertest/difference-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..0b8070fcc2ebb84436b53d75de806f2e00f9623b GIT binary patch literal 7812 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%0D5{ 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 literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/ellipse-arc-expected.png b/tests/regression/throwntogethertest/ellipse-arc-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..ee1b7e40f48d393bf4f4715887a3f3783e2308ab GIT binary patch literal 2227 zcmeH}VN6q36vzLsyh)2wu*3$1lvgH{$TrF{jX=AVwU(7_jV_~s!LZc|$+ifK#6b%5 z1(!vl8EnA_VQHtZ#Tg3^Yd}P-$>y9Lrt6H!7B`_LMK%R%t+dt-c3b0zCHuTA8K2I* z=bU>_?zz94|LxeOD_xbeJ_*1o?W^iH0UjChz>gX8dX*jE*J#xxZ#7YKzYTv{Ri065 zocn|qVX`CxNk_aQ{&AKSr2(I)A!JNaJaRZQwHo{``-vIw8&Gs@zvbwC&&u zRc;*2DFGK~u-n?A-uE&m%uB{G=iUWP<$~943t9VE-OS;swpphJTdZTZHtXAD)Un z#Sy)D`nmbFtV&~verr1^@A|-mJ?Uge&V<1XVr6~l34Z5zy^j(F$n94@yw=%ICJPPa zSd831LXqQAnxf=I1xf7~r!Tk?ChsnwqM7|t@y_%4gGdS9q4CCJrU+UL(9Mq%`A@`k z#6kQ!um7(_q(9`TUQdnd2~iB1O}#MxST&Z#gy`mfT~+_cTr0UQDe{j90Hic-t!R9` g-n5(iO7McLpg>kA+cGe-e+7v_yH%$ie8tfI7ipIVzyJUM literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/ellipse-arc-rot-expected.png b/tests/regression/throwntogethertest/ellipse-arc-rot-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..19efeaed1b85b66ee1168bd5df93bbfdb3663858 GIT binary patch literal 3719 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 nQ@g%9S=m~Ve-kBRkvXt;7uBhFCq3z(I!#{Q literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/ellipse-expected.png b/tests/regression/throwntogethertest/ellipse-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..9157607db450392a580aa240e29a9f7bbc183a15 GIT binary patch literal 2836 zcmeHJYgAKL7T&pJ2ry~{EQ*5HN)@UMUO@+SAn^%UDQG(_s1%|gp<)>y2--+))H10h z6{o=hg%Yh2IyFF$Dlh?ZfdZpa9>qdfLV2h_Oa#P0F!A2>rn8oR=D*7y{yKYo``hd6 zv(J8=+Zh%-+hL&t0LbO^Tq*uh=s}pSHt~sQQck%Q2vvO_+g_OBza@9*NHkLkn^ZQFn&alon<;}Bo+O?{; zseUM|#a*(}Lm(*cxs{X>KYg;msQW1eO3~ z*}=vvfUWi*0@%6q4|wh}tAiWQ{Q+CGb^yx|u)?p6UI*gkWCQ{4{x9WTWTG`euO}lp zUtCdDuqW`UW8;_K5BC1>T5N>qzm?+`!dgTiI+D#~K#8ZXA>um+1dhe(O5d>$3IU=W z&2o~6fv{*kT{aiKSI&4`W+GrdFcutUJ0~14qvZ2hZeIcB>=I*Bxq`v_!JDeSB?6+z zTll4|J`Z>X*R`%1S)ntw!UdfZU$)1cefWxmbFZ(;*zP#JPs?;GXcVuGRD9T6BI@8j z80!_RQzq{4294QwDaIW!R1w%n@oL z@lL#)$s5;m4k;;wXL^$#NS>WSSq#meJJ}<}-Tj74G3ax&EFJd^MCm6-qYqjM^P!Xd zwr7xD<&r(z@Y>p`^uj5$xbfg|y+di8p!`^d?67ErBkyq{-0sPdMaD9(L^H z`XLjpU1)R_C69sOCPTxvup(SZJzYxkeBw+a#fxc|R!!aO$Ky==Rqmi;FKQj5%*|+? zkPaAz^NTD$S;(QbBws-zMB?l282C5n_tuRaTQQF&C(30zA(X4g6}EnZgMzC`i#|Ba zP*OR<&bDpCZlDX#)fO$H4{6`XXS!eJ7ECMAHZ&1S|o5L~J_VM$V zyNT#B4CFQqBpdxTX+cR^rm~^KGMH(3ur4$mhy$)^OxWzRkgOZ?OG6+}!f}+&CZ47S z1V}hh&2&W+JmTY2Ylo^WU~|d+@A~{r^SoJ~S0xx&s_>Q<>RZ`YQe`K03S7L1_GZ8vM^+UGRntkb?U7WTDSZ1d`S zkpCW%!LGimN0ifp6|L$brholVL&DE%ps4lYM%(E}i7{!h$}|m5=C+c5d>v$mGy}#2uj5Uhu_Mug2D6 V>#h!;wWW2ieOuVplFhs`zXLiFjAQ@+ literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/ellipse-reverse-expected.png b/tests/regression/throwntogethertest/ellipse-reverse-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..5e94cbc027583a0d5db5ba50fc0195af1fce9eab GIT binary patch literal 3007 zcmeHJYfzKf89w>=2qX)FVF$qiVZ{Qg3nc1VSTG;12!cUcafB@h;kJ~bO_*S~NfLx1 z%f+Qzm*t|8&MK%Q!3YbKjgYtq(gg&}a8n{Fq8JHrk%R7V}S`0JeW zyw7>hIqy8@oTu#9$HHuh+lc@G+wdbH#{qz_E(EMMnu6_zL;$?^`|yy1CvtI1itdUu zD_6lXyJq)J_m1;th~ecQiY`PFR}gCwd?(`0!2Xl{K23LGCGY=m^0yJPP*TV*QiyG( zCH6af%R-%_caqLK|Ml$9fPdYL`mUmeJ+iX)ep_1(r?gwT_neT^5$j?Dv@z$IGj( zIQP`aZRl|$tv0viAP^fo&9At5&l+QT(4`UMtKlbleCTQHHvI*Xq|H|#jfIKh?bX3R zS>E4)QGfiJaRTO3A8Y0kIkU5dhx!JyAx@L`VFU#a1!rCJ{dotAqGFrVH9#DhSuYyY z7#VY&5qOwiUTrg-=EZ$$q|x)|MjCN(AAriN$G(!+#brX+d5VH#1g50Vjqybe;G`If!>jTCt!YYRIv|B8HJqf7>F-Gd6fMKbav+ z9bR>9VQwD4BXzE86CR%-P?ksc#eK9TzUX7>a=aWnrAt!b3yb)w>hx19*(ooqA=yi( z!u?8BdIG1Uodndh5CT|eGrEKCrV4BKZ;5SXej}{T`MyfvU z>P$fxS^DOAmyX=Esm1>(QfJ{^?(>Em=F>IyN$)FFbuM6 z5)KkAW!k;rx20=-l9BNf5W`gGCa(g)gLMkt>cVXaA?BjB%q%((RevhybG?;(q&*S= z6J&|q(cvo~bU@<)Cfj+LOuiyXKYgvsLFRP1jw>0#(*uC9b2W&;$<#+8m-bDASYH1S zMjlPpJ6C7m8R$eq*kJBmz4-wz&_Bpju1L4>bOMVbxg503ub$hVqJhPq9u4D(jn;rZ z{z!rTEh-_)xqsur($zkNhF8yWG(C%Bt$*0$sJ~p(K7G_%)lIP}aPtWzyPej+td>Rz zQ@a(^HZ@*FrV2enfHL|UP0v{RpJ4D!RgWV54b0!9sA_#2t>gtUvi9b5_Txg{VMBsr}HojO)!PIoSx`@Ev>-oDvsL^y?x1-h|DEgaE- z9Nte>UguMyx5$=-dnkE!32*PcJmd?uQ%*UOPbNRY`FmOvjddDWmPNp3yZEBpT|@Dh zATtwIPI)@${FLb!U!8WD6Ia~Y;BE+mhCiLhh2cE~Q!S{4_GSW*sNU7(B;llIn|KZ9omsT6}9|!OJ+J9?Y*X(vNl=Gt2yZM;;w$DBs jn@wk?reO{p?4eMplmLqVwHL*BEC#|49}8*ulyUy=;hg^y literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/ellipse-rot-expected.png b/tests/regression/throwntogethertest/ellipse-rot-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..3dc3938e666e0eaaf986aeb9f174ce586d637a06 GIT binary patch literal 3751 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 literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/for-nested-tests-expected.png b/tests/regression/throwntogethertest/for-nested-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..62da66add7d178fc223a38b4f22a710fac96cc1c GIT binary patch literal 21492 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 literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/for-tests-expected.png b/tests/regression/throwntogethertest/for-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..664155545934446cab4457e447d5d3dbb01357bd GIT binary patch literal 6927 zcmeHM=T}qPx7{IB4Hredh^TO{v@0Oe1%inUR76k_l&(_508x4i38Lb?fGCIx2*id; zF9xJ0i1ZQ!X@&%l-le3FkmMci8{@t4{)6}7_bJ&U+2@=+*PLt3wa(u*=PjhS?AZbU zkUn$zZfI7;3lbp z$<$%?g6*4PTewnY<#rqRaalkhiUKqM4^IMc_dl!uAo2f|1@1p2Um#Z{f~Smq7JnI+ z{#o+T{6@>p53gT3*2;WAZxcbM#QV*7b-_hJqo-yB{(y?x7fE18Mr^aCORDF~`U$&h zHKt>L%|#DJ=^l#vHZbr>d*O54wiiGIeP#jw>vx)>`B|V|SkdICL30cjG`&r`X zb4SCxrwciwO<5#CICs9c3@%u6UF4JtXL@UeY`-TlKcxU)fzBAdnJ+pAzfJE}%NQQD zW?=Y1RHO1Bq|YeFg6>jo0NA()M^<{|M7irC>%A#9lH7z+M@WJR`*8+VkIgwZg$k5H z_oUhvdTx*$LGX|hHR?xvLY9U3t~G*)BuE()eC(9o9SqECxaqokY?5K-K0hRzvGX+i z_wYsjsGo51QcxQ@VB{=C{d)r83P250S0&{a>9 z!kA?GGhia2nF9gI=d)-cF zClIVsO^=jT9+GiBIKL}B7yp)p3T-PTbCjr0$bv!3e0h$&aQRVu3|=5ts1kRSC$)+d z+O5aEQwh}Ty{Xx&6g#mRku>LPiuI|&PwO-@I2Ku9xSc|}>HEd=1p$V{zgj3PZTgEl z6jxNlA7h1hVTj^dIp5->?Cb4dW%hRh2G9G%EMHf28T_s{%%hg^*SyFrVZTnpZ}SS$ zClj3(m*j&5JbdkP=OY-WNs)XhSH$c^sL0{bNtyZmKaG&y=uF_V);g0i5BC>$J4quO z08GG~5q!rPDb*Bk?$Nxnt3tPZO#pn06lJ;$?|T9iSYMWUiU#WMUup6a!Xv5np0p+T zJHWh>o1P@@gP`PSF-hL2u$I5x!;(*eJOQkBDqp492aSiRdoCF<5$N>Gp)lWdvgaRH1`TWIM7_- zs*QmOIe&Tbs6*WQR(orhS9~#Svdj8;|DdT5YY${y#;{h?ZSu;-Y)})TXcYOKkdv!$ ztifr>krMZoCe+U2#*8BAOIn^-VfPu`$Ipmpo?`Vo@djDKY$}hDFXn^q)3tGv%IPIQ zvU_FTAca;*cnwpdI$3Eq()`+rgPvc=v4el8gk=5E{Jzsr1fRuHNHdy}A=8?YgB;m1 zW<>{TmVdp`RtUplmpV9OsrIF5;r+FjW)ecq(=!;Kas5`9teGcm6B{8}_&dfGF39CR zX3r93@fC6vQfp`SpHh^2qQs8%9c_cwtDH;rx`H%uhy>k@;BD)(=s+G+8hsn8Un<8x z1-2cJMQqx9(6WGrzqA?A+Z{n$DQQ~Fbv5nO(*20%z}BV|c3>;z%TKWzorgy%sW%pj zX?^_KcMQRP^Whj7lt8nlcx*C!@TiE&Ho`CY-B!Ryh!D#Y7-Ccgx-nwfxDqZaYQ<$( z`_oi9%?`yMQzS92e(4=Ps;IJ?0OH*Zb_QOI3KSNJxXnEGUBc=^V(-f;6jP!>VrL<< z4Zy^(U~ck_Xu2A}&0-J4v-bMV9LofeW6{ZCGC8kZzv=o28S;{fKpskZKAnUrx0TM$ zzUb-|n)Xy!bB0l^>#OLew|Irp`8VLAZvJGCH+sIJrxx#y%d3vf`zW01?9Ez6)_(H1 z^lf1n`SIn2FiA?beTo#qgK7WM(y>zpCup(6#8jG?R@x5=N3^JdpK#ZW7DGsTsmHBj zwC}oRxSYWv*a9Wc8%UfC_J-dmvzE|=-1J9Q{les4OFo{PHN0NmHKrO}rhr37^eMm~|v z`$+5(*8=1)O&PQV4et5HY8c*!n`B-fpWQ=nNKsG%W>yMys-5p+^e|g>N=5P2Arbz3 zzGAiG_{IJ7ip*lZ5x1wwPDbErAuL;Zo?gM+>cJZl`Us!5^$^^7a=g1Fz6|3cUHq|c z&bKSN_ZfDcw8hY|cN6m#fm{+z*TC zH;Jn&t03=3RQ<+iNUXv4z?t%cj^LJ-^fZ(`C2*CCF`UtqTGvB2X3ATCLpQFMUfoOx zeJTJ`mUn0n5>f&`nfq&u>c^;D%=`&I+`i=ehbWPXSVN#O@RdSApIocMG+poThHRb; zI`%ouUDj}sFCy}I@vETru{V50f|zc9zHkjW`5wiy!qe~%Ey+z6Bj(EzU^nr4ryB6vu&?~VA4jHI#5t@mppm($^?pBIY zmG;!$B=M)pK%pFeEtiZ7^p+}HW0ck}9GmO5CglrkKZrUbI6z{wOdm7d|X=>C-pCsbl`xf~K()`)9F=KgRUT9$n!oPhH9gW8R|P`27+7 zI~UWm;HrTE{u$n4vV`Sk!7Qt$i-&ik_UQOh9>R-?La#lp*v0PfiL0B2+dzUefVNK#%z)*dr2?kH8HjYZ3QxjzzL+egsN6I2HbaEu?R0$)pfe9yqw zArns`1=!x?VY%)K(YoSFW8?cH4#(~%z{=MpcXZ$Reg^(v4@!^oE4KSfLHLnm9gYJ9io;kb^OVP#Qxg{K za=|k_u+EO^47l;7_SwYCM%Br_K0+j{xs;Dl5cyTtlN8jt z=KEM~-`v5yZLOQ?lRf~D#z{=PMg7QIvgcWHBoC(#&Pot zH8WICu=pb+=b7~$!g%9XeWgGkYpqx@wD4g(`A`6Wgy+H@C2d?H>3Fz(PBaViWb!JC zLSId{xHvg)YcL|V!%i2p4H3$<1&h;qg>t`+cYvEvzV@04;cxUw5ZhInqeg0RbB2P? zB1J6Mok`KkL7SDU;=&|&S_W%jSr^2~P`yv|x|7Fs!vN8%u2FrDV&5-ytbAY+FgcrT?t8#JVty z+dSbHtNd{|j2bs_i!!xUALA9|I_MGVGgs?)g~M$;K>JcubR|^n(4sHdizkf{Hb9as zVH+{lyWpltbFqrJihHgFCn8%?ppK|Mca zzTr}z_N{6k(#{OE1Saaf;9^P}n&w$Ju@#-q5f6l<>K$nPWxgEiS2eZ#z97)_vp=jE ze>VPjz>-md2IyG8V_5Syf<@*L$AUx*4)Un2ya-Nr4fYZI?5zKuuy$NZ6^^D@)%2OmTarIEFJyh@ zCknDjFz5Hn1HQSavhPz_)wbibwlT;~DYVUiF!^ODMfydW}m(?U=Vj+I|C`~`k7?%X>n zO+HzWC|v2Eh|Fp!ur`d!k~#l7w{<>yzfRu5aAa8vhH~4&cI~v$y;qg#ILS9vl@-{g zk;W>v7_olVdWghkJ@M=PqmbEQsPt{+L0rTPMzsWX?x9vwAKxTx|5Nvw@-6!a{D7+D zxh!GBI%#;0X@zQdkna%eh(djHv@9CPjCQ{&!%x8ng?n_qer3mNdoLi0m~k9x4-7NX z1=X10j}BtJxt#3SKL_t*IS23yFl1Hs`{!0j`;yB&mdo6zZy4;6t9+>VU0)n&-!^=s zgqz$po^d>2xQb*>extCoX2=$!{vwUFyn9VU>Ff(&hVXhYYTCm(921w+eb(>?+{zB| zcF3-=lh2JaT#epg9DM{9OS^>KdgW1dYrAdChVVlrD#J&(ZGGgS3lZ8Ch*6cKgdwNa zQx3w)t#NW`&taBb@pajD!mn6i*(tlX$zBELyVnr6owDHq>WoP`D)3`)J}l(ZhWDIY zq1bL@p~?08DMm!-LjMeXwR7oqXzI&>USaPTDJ~+T6ZhzlPVG#} zPe)bYQMQ^X7uSxS>1mQ@a0rr51L5B7$9B!wzqe1al)jq^)xmSGj%bwTlQ$a$ z-+n3sx7r!uqF?j~es0n*Zwf=_QEPr>!*-m-{V9mcnJweD7>3+dtw0En`7}k5wEx|2 zh8y>9EKWq;DKno0msVpB`LeuC3ItOwp*;jCj~|jBDl202T9^vjS~9!A!)!lMXv&LL z60yn4Z?e-iMy`Av+#?pgc;84m`et^58=_l0ul)yyAMeK>%8Qy#2l3Rt8;QtkTlnq1 z%<$9lMf~mHLz+IEB(QX2*8e+4vSqZ+Lsy?NzTID=_C;oEpCmxr1RVg|pn39kpniWH zUL_rU_ul~vc<0Q8<|Xnw5Bv#iJ|Hd0YM0S~sQaK~6XBR0pt9hghUb+BwR{{JaUT>_ zn0d@RRRc$V6Sp-Ix%kHMBImpM`+uiscWLvosiu*gUFJrNqnwk|)^y3&j;RJQIT}Qr zbILH{zgUeUL+9i&^xm!J_GmmnD@LZ))510RgIe&GMr_UQ0ndp<4BiLN{qGhqP@~j0 z1i6XjD1>ESJ9qce#w*Ku+|RyV;m}rWntr;0uc%Etgyl@atEa>u-UCnjCdA%G}^aQx^PS}W` zRhY$mG58N?P^Z7i#usqJ_01krEXT{EEG$6K^)53O%fD_`5jFmIsj7sP2zmnEr3Ihq z#;Sm>v*1IvH0<=he2ozW4!sy~PZ@w?QvW`e5&$Rv1hzeWH*)3NK#w_^r1$trYRA#p zCu*I0cKIj+Ti5r9E8MQ0|9KezzW(=a0s!0o20&Eh-)sG6^&ceo_^ZwIkF}}9tpMUi o;HfKt=dkV`&>JtHdss*3u<_v|Zzp5Kq2IunQ|C_>9Cy3>UshvtX8-^I literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/highlight-modifier-expected.png b/tests/regression/throwntogethertest/highlight-modifier-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..a1ec8febd16a285bac6901e947153a40eda552df GIT binary patch literal 12885 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 LpM3iB#n}G<2Cp-e literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/import_dxf-tests-expected.png b/tests/regression/throwntogethertest/import_dxf-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..e8173d93d8578261b610fe14d51addfbccf778c9 GIT binary patch literal 4414 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 literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/include-tests-expected.png b/tests/regression/throwntogethertest/include-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..5b1857bc843754546f861f65546ce5d4db787212 GIT binary patch literal 8317 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

#Ir{G{@oEYN0o#8)36Ds&_VyGI+m?4-W0 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 literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/linear_extrude-tests-expected.png b/tests/regression/throwntogethertest/linear_extrude-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..59b61e76aa7811ab9928375f7dd2cf0570d33537 GIT binary patch literal 8648 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 V@ju_bn}yCGWL{YCrMdE5{{aVWHf#U@ literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/minkowski2-tests-expected.png b/tests/regression/throwntogethertest/minkowski2-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..a511f37ec7ad0e801bfa8ef744a3640b0e919ac4 GIT binary patch literal 3635 zcmeHK`#+Rv8^4~JF$g=-zU!33uB5HCGbrcGkSN5oI=7oAIhK;8q0-FAmTbGcu@V}i zwM#-x>n)ox%~+(8j7SqPZAvW0X&B9zv5)q%f5H39{;)qi-_Lztr|0^7@9%xxne_qG&dtgroi(J@@hq+LzogmLBQ{tI={ z*~NtT(^Z>DYeo_#>+%JypRJL1`{Y~HAD?KaPtdDA{OHj0Gig3EKgKkqZ~U&jr{KiZ zQ_dr$35CHqcDb+L%^Vl3`n@2?ar51dc9r|;)&zuBY~fEse2suj$ALA#ily(7VE_OD z*eh_bhwzee?cpT7L^-|@d%hb zl?}Dq5Ct}XDr$X5bs%J$?T$yy%F$K7C&llF@l(kK4Uz> zw)SILoUjHlmBAWlLTv~z$`occROmun@$gO@!geha>is~2lGtuxXarhe;Sc&bmSkH% zkEDKzqeHnuSsCK?%?D4|S_E+R!w(rFU|`*K^|O{>q+2m_%eeALjcY^Oh&q zNnUkMcLb?3Wu>kiSvG9@=O+h-A5hu&P!8skn3G*?yQk{@DA}^F;KgXz zuW!y3PEIy-{NAVPv?7NIhJ%7v0PlC=BRx?QTld!iMo-mu&d3#(VrBcrvn>!3xkQxXGg!P-x^2Xe)bL#iHwbUOmK5rPxzcHBF z>F}#st^VUxZab5w!igxRYUQ4D4Ka^vZ*wez{8|&Wlx^?TDXzKR{XH9{=CnNT@_PTDNSE)3i^q1uxT3JqN^)CfRU5clz zxkZ?3ZtvSL{@}b8`(vI;(49myAs`}oD_mhL_jVl_@AZXE5#-eJ70V#7K|B{#u^HkB zcf&C4))$W#&wsxG{S?ueS)?RE$L?=|e%*6&AU1>IX_cB3J=N|;&j8<|wKU?{CV5TH z*4;Re)xep| z{*SMyrj}!?d5Eo~B#$AS!dQ3NynH1*`WSCf%;dNG=|F4Mw$od0vhF6@F7jh~LAk+2 zDzW3Lc~3%vs5sid!6;NN3b8*Ti`bjJ;Hpb=k?v{($*J4kAc@iSOJvoIwQ)?lO`VV! zSK@U4Ue1h*IxZWIuAX+J?3rzv6cl*OO5ZVl-pRyR6eT-8-q;(+y=4tWC*sRf4(Q=f zMXcyZL-(lg-IvF1CQsfId-V?FYgis+e5FLyJ04QM@KU*Tgge!wxIN|)rCiEpl9?P; z2K~%xd~;NQ;bU2+C$g)xzBz2>jv(86JI<7+l79g{DEGqWwJQmW>+(2LkhHMIZM<9zuh{jyHYWkMSbn8Dfcl zDH$)i7@<}2j8EM_WUt#uKsWIhD7HX&i8NZOGadl%+7&J9!-`07y_H6=PE|zT2fBYY z9+T!=1dTy>s>&~LFMS2N?7n;it#P-2QelE41RZk#2CFv$hIU|r+ZX}A-C7Q%Hwd)7 zJ(%UVwJCcof6LuVecD4=7(=&E^91KzmRR2l#^2blM%-gY5T0iJt5zH$F-d>oao6$w z%>x0KlnNHfZefXaqwJ#Q`pmd7pOl8(7X1&cygWVbJQp5qU79R?q!hB=1V-aTp5OLXsL;iRJqI_Unn!dMT#Z+r{UOm$JrNzU~k07f$`0 zm`)fY(1<-f>SlzZHjFGnR;?|G4NPVoT#215++jEPkE!IYvNNoJD3Yf`42@h}rwYx5 zOH7?-+aRLEoF8^vF=hPNFvc0Pbgf>OETpJK4u=UPVs>7&o0?%Y6oi>s@=0+al~`mct+1lra|4x$KJhl6yTkuIq zQrS_jhd&|9^qhEy8CHhu9Zq6DJeI(NkDm|9B?hg{!D7mv$x!NZ=f!_RBo=dTiz3ic z1B#jr@@vSlkw}pY-{^!X*u&`co~m~&Sr==rhE@NEkc5hIa3FH<$XXnAb^ckSE;eCf zMVWa1J(shA4*Hz;Blo|<_dnzfGegbT_0%&n@KF~47$$Apl@t^o5sAGD!I9$NKyjnE U{xq0Q#P(qG#$ey;KIrK`0s2rCa{vGU literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/minkowski3-tests-expected.png b/tests/regression/throwntogethertest/minkowski3-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..6ba016161fffa57614e60b05372857fef554e751 GIT binary patch literal 4455 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 KQ1Fvq!hZlnKcs5_ literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/multiple-layers-expected.png b/tests/regression/throwntogethertest/multiple-layers-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..347bedb5a2c0d1a80a7a237769b202d0acf943c3 GIT binary patch literal 3959 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 L^V(Xtg?aYhY4*6< literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/polygon-concave-expected.png b/tests/regression/throwntogethertest/polygon-concave-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..3c6c2439146ee1580bd40f7765618b526dbdfbd4 GIT binary patch literal 4527 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 L9S)T2r(gOXWW7EW literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/polygon-concave-hole-expected.png b/tests/regression/throwntogethertest/polygon-concave-hole-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..2e9de4edc4b68bd823f26d519f8e64151733faed GIT binary patch literal 4136 zcmeHL`Ck*)7Qd5;AwbZG`bcmgC@3l|37~)!h@wz52)5QPB8lv7gja2FgPDLWSXLJV zMGZ&+wJ6eBwWONS2Lf(DqsWtn#G)nXmt&)m6Z z_Q$S{baHTY001~eeZ2e=0B97X0p|E+k8Aij04AxUmM>kq6B^Pq6?;6Gd9z!&F!eV^ z)AoMs@%O2^$%UocSGB*oEy`amN|+qfUqP>*YuR0Oq&9QU*?MLD^B%I_Jfk|QH2T5S z2U?%35tF9V3H_*XCghaw0YEh!2m$zb_It!T3;+{=C^{HN`vbm0ze0-$6sifJwO2{T@C<+gkBI}18|V}9x*+~a}hd2U>yDJ>~9(W=K?|F$aj4T zH}anoTXw%M+Jw0Hca19I@|gpucKWks*Xg=dhJL@UxgA$w}v!xa-TT+3w9O*PF9V!gmY)_ zDC~@Glck!vr84BabH1G8eF|oW;+@xS^w(&M`-!%P$j;Gsz4Ko*Eq6&|$L9=lS|(tI zurY@{W?NGY*8h*)n2AKMv~>Quu%6{5Sz0rltK0H! zv&%Huetkr7$KEsuI6k0~b$3R=CaWSO&uO(J%dSjlzq*K%E1*d%^Y#=(zfg|pH&bP> zR1j{dN(Xphc3$i8M9caYCdH>Y`g_&)H_ca>UR|jsjO5?EEujermVm(2GN>|{oB)?vnp;_ba8TdRtw8sZTkUg3EU5VRv zpX{cJ(MNJ(XiDy}=2nRoaESHV+P>eX{hKoSJ~3$nkrkI3`~J+oeol7v*bvNDQ(l(% zUA?_-wv7CP>6xZUsG?6epE3r#t<4=zPjt3)g}?0>+)){%6yNK3^Wf;OlP+(^R_vVt zt5ve#O7Y!zsCRgFKuQ18Jnh)P;OiydL}*Pes;bw0LBkO%vo5bV9^WN_b|PosWSuon z8w>|uUGwzIpt2x*+djVA*NbH2Ek{>(5T2?VXq)8;%T(9!%p`o!z$XecwBh?FFS+|> zyty-TWk%cdGW=;Tv1p{xSlajY)yU`Ky{{W@PRqPFIALf8`Ymg#^%EJw43AU0yR3bl zbg&-j_5_aHE7sbxM9K@Bt1|4?(08qPfFmJUqCOSk+q7NA+H z4}LxiZT04+{alWjCS6Rgm8p)}3MMLStxKUV+Oa^f{;_!T{0DV+EtNJ1reaIK^268> z$xT)-ar=es3;n00^_x-YSs^(2=u@tjFD^r>G}1cN*&MaXYYN(0#Z4P1!%QWCo3~s} zvD$q_exhnffYM^T&#<-{UYVv7Db~D@+MES3$OF)hYQpl3ZkEFiOH@~mseU>;9>B!D zHSY>8rpb`B8WDL)2L2JAD$(mz&&H-3w!hdk?n`UYGbF#WE=G&5o5W|H9Y420p>eyB zuU2v9frWmqC*`-5Qj492NWtv{CW}vmBQ`q}mLZm)^~&ps=6e>sh)lQxyl?ns?6Op9 z$R{2{HeKpxJu}o{+2C0_w%zl>SO9m6wbJNzOGJ@|fda1I@Uy$M(%Ys@(KbpZotbN@-ZnOscdiQM|BA{mY@+T;zcr-aHWfHcGadYji8zM64_qwq8z?VN& z8K(X9vm-%7!4JSY2{TET5YuknuxRJ)!d-Sz+WJ>@VATuE4#bH`7S%rRhgT>(67ZwVRB4N@u$50il~0l-22JD7jxw#q5%Z?`@>?VuDr`$? zB)CwP|YP&F+Qti(Uq0cYAU8hz_@KuumH#TRWpqZY%d~(^s2>N z!}&P-n;odptr!#v8O;mJ(z>IKx@akseCp8s@C|9T(7EZ`y-&j#-_2UD0Mk)odZ9cT?KUBLDv1) z%XB6iB^W2bK@5Y9S^^w;#bhNKSC^w3kz{ef@v5L_ge8QGO zOa>F@!W;Jb5{{7WTRPx+4Rsi$J&F&Qpzi>r?u@0Q=Q^;0quF%yKnJ!#6c%C+d}DKr z^lAV(_|~b6a-Bg%!zZY7AY+X$T;~R% z@wbvNiyuT~o}P#w{SNROOw<5T1mMI`%a~0EXCHk)5$vaUhyB%^7CLJDEs%P^reQyr zZlT6VAaPjelopdL*@jn+q{h>PyD7g-bdJvj`n#d}Kj=$&_esd@;7Hbm6HGGzfW2eI7du4K hq-5$>0QG$l2>3z#z#pFFGAJ8}TCsZh`DK!v{{T~SIBfs` literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/polygon-concave-simple-expected.png b/tests/regression/throwntogethertest/polygon-concave-simple-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..78883b267046691515d6aa279bc034e3dbde6e55 GIT binary patch literal 2645 zcmeHJT~HHO6h6CImn;TNLQ9Q8jf52j)P_(Ih61602vSHykQQ6F62Pg{(g6p5KyE+^ z6vxsMWds#E{!Ci|aYjWO6BlKMP|DCgh$uR&+TvJH5DlOR=_b~8I@8xq`{2{=J?HFq ze(ty5y%m~-7#HXH&H&&NyDDl80M5VSAhbVr&W~&YaE92Zh_&0W`#1gfx^MUR?AF+( zSdQ>=|1hAOS}KuzI@`<1@3em#Ij4Y(G2=u#AuKlG(h7xF*FCwvacO+=&g<_d-%PfK zC8~~GTv_{I!>H+AWk<(^07B;`u;4|3+5jK~pm7k>09qX600yDM97O>F(0Dq`b}tU< z3IN{x@}rl7aPZ~dXM|E+?V#LWOymj56SmCz1zZ2N_($qm_qnqBO*7*|vu$H_Bj)^N zW{cw|$Z@SX`J`Y60`|R`hoe0gpWRwvocv}Z>xH3(7x?f?S6>k%5Geig#`E)(WzZt% zeHQq*VI`asl$Z-QYUGfbdw3BULqox(S_O%-5%cm=jyV|@L$CY(MWj&y`RUyf!GqP{ zmi|-3T!Ire1~*2D&MG9Zc}~9%w9ZalSROw^z0frFh$%3^&_>#pTO z!b-Xd3;M0MZfcf?C)limx9*>qJ2Eq(r5HuF32wA^)Tkg|H#Bj(91GIosRx<{&O|zK z6(ZD>6(n4?XtW(>E*a3uxSj z(M(8oXqOZP*bB%Gby7h6a(mTFx_cGSf1GH%bIICjZHxdjTUXY4GVDS%)|=z2Z@QaF zaJ~yS>>V(mC*~&)cE)g-1anwR=}@!}hPSvL5sBhhsSYJm?Dssf+t&n-k)pIiTcBS7 zo7dnea)FTDWq~0Lo+$;I8PK5<6x-biacrQFo%X@}sxX2H*P*RGSPvh-S#+pJj6UA} zXmtEaS^k}TT1}=NgzPIFHFs9Xn8maj4CqZ6R{_JjMJ+g8?utxWT!%`9Y*;7wX^Q7# z8hmoMC{M^Pve?&`T~WF{pps%G?_}`1oLQxhl%n-~6Tt><6(MJQ1#zsqDo2ECRLQtZ zO%Z6%xAw+Fs+J_YfvB&-1~tWn5P0!r>2nHZ3`Vi@ErV_FGBTcC0cjxl8h z^s9xvPJoOVGB7mG)*3+8r^L9$$K&LJ^)Y;H9**qmqsc*6z9NrmeK3KhzQhb%Gl5(* zxSOW{w@;!=TfADx57|8ZbY1v@@R_mJqOAwAC`h9P2L~pyD3&HrcT#bramzqG4LGZ} zuwhN7N7CJF&N!0hgie_^^Eih(`K=s3R7pWd{&7N;LruNz4eq7VJ$?CMiBN@2Z5cR1 zppxrF)Nlw60og%<@k2(wF6Px0{S$5Nxl6HzU*gGTAAR9nAkfhOIw*rRD<^{-pC69A zkn#%Ze)xr|%0UD%e~Rfp>gUU&|3A6|brwO2$a792s=fvQ;=Jh8y!cOa8T`EwR7!t; WWw0{n=wJ!S`ye(tA*%UprucWA&7*bz literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/polygon-holes-touch-expected.png b/tests/regression/throwntogethertest/polygon-holes-touch-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..9fb4806d4313c1b8b0e7343f6961b63138bcdc83 GIT binary patch literal 4147 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 ZqoZ)X(B<5lL>AJ)(nUU=#qP3${{}9@C87WT literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/polygon-intersect-expected.png b/tests/regression/throwntogethertest/polygon-intersect-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..81eb514a80499e51d0220fdfae2a09dfabbe3f5e GIT binary patch literal 1810 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&t&wwUqN(1_rjho-U3d6^w81Ix;dS2sCWC z{Lhznp>0a{f%f;izVk5HMl-Q_7%?qb#K6GJ zu;2(-7$nQX&>+Oj02Ch8G#U`#^g{a-k+9$-TCxPDR-m=-B^ey1FfvF06FS2IU~(Q! zpQ9NSR5Xp2SfIjr$OnX+b1&P=02AQ|V0|V~;u=xnlb@0btn?U+3=9noEDbD{JZokH P$}xDl`njxgN@xNAEQC~E literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/polygon-many-holes-expected.png b/tests/regression/throwntogethertest/polygon-many-holes-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..3ed26426dead425299be988b265d9438dc9d0c5b GIT binary patch literal 4854 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 HePaFxEi#9z literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/polygon-mesh-expected.png b/tests/regression/throwntogethertest/polygon-mesh-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..81eb514a80499e51d0220fdfae2a09dfabbe3f5e GIT binary patch literal 1810 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&t&wwUqN(1_rjho-U3d6^w81Ix;dS2sCWC z{Lhznp>0a{f%f;izVk5HMl-Q_7%?qb#K6GJ zu;2(-7$nQX&>+Oj02Ch8G#U`#^g{a-k+9$-TCxPDR-m=-B^ey1FfvF06FS2IU~(Q! zpQ9NSR5Xp2SfIjr$OnX+b1&P=02AQ|V0|V~;u=xnlb@0btn?U+3=9noEDbD{JZokH P$}xDl`njxgN@xNAEQC~E literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/polygon-overlap-expected.png b/tests/regression/throwntogethertest/polygon-overlap-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..81eb514a80499e51d0220fdfae2a09dfabbe3f5e GIT binary patch literal 1810 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&t&wwUqN(1_rjho-U3d6^w81Ix;dS2sCWC z{Lhznp>0a{f%f;izVk5HMl-Q_7%?qb#K6GJ zu;2(-7$nQX&>+Oj02Ch8G#U`#^g{a-k+9$-TCxPDR-m=-B^ey1FfvF06FS2IU~(Q! zpQ9NSR5Xp2SfIjr$OnX+b1&P=02AQ|V0|V~;u=xnlb@0btn?U+3=9noEDbD{JZokH P$}xDl`njxgN@xNAEQC~E literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/polygon-riser-expected.png b/tests/regression/throwntogethertest/polygon-riser-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..ecaaf5a5e6d5465363c3a2c8285038acfa80c197 GIT binary patch literal 2883 zcmeHJ{Z|ub7QQo#Aw%PuGg84;B~?eDP&3%>0#am zn#rr<&+^u$SeC6VJaUco@UX`F{yioGdbpa3@5avDf112wv^(B2{+(36eXIL&PS)zj zriC3v1)9vR;bw2m9n-q7V@|d0j%_Ae0tZ&KFOacS0MR4}0IHP$TXB%nt{T{*^JZ2D zAO)a*F(a=6Kqm#4`6cKj5iY&_4;10{4mmHmc_lJgO-U=RlH$o?i^KkU!H3Bkf~}R# zDW72Xft8rP>cWqGKI-P&v)+K`{1!`paV1kCpkT5lRY;ssp}|F zM~WZ5_om@ooHET>>UHkm9|}t}`z9ZBgxgI{ng)yy+;*!C8@Tyo_>YvBCuO_)%}*Z zxc&`{zn}v`SWgQ*i(Q-?p&&L$i%`GD#rDkP@<^E+241Cw66EF6tEn8G5PDR&cO5X{ z9N=s{C9WlbYb4O^WHDz+DzfTW7o;pHficTuoR~^NZtKs~_U#!B^~>H`(e=wl)Em^K z-V;?h&60W?8)@;wE^dYpl`G^>J?Gju;%$^pinA}@ipQ=3&$lP~sX+IUu?q#gUvw>Z zR@DeQ7Q@cp1RA1`U^ooEUG|?8CD()|wR3kW-Wm_8@+6Kam5j%d?GYHzsNeiyN&%{Z zO6pyQW3aEuHaC`x|F9ZbDi+R@jdGfcX}*epaeY}n$1L9|zKIEU{85H{PW+sN-yfow zC>+894abqNB5~+ALrCGiKbAs6BcX00+&2Ngy&;p=&~i5n#y694IG=(~M*~Z?#*)Su z;1B48tx`zTy9$}fA~WmCBFQxf_03X!7AXnR%P?VSGETrVolqo)5Gf}b30G&%67Z}0 zpC(Mq+Oi`u`ul=81&DhSFA`pL4?gydYfDmh9+)xB4d!Q@CqD3nQ$d%hM>6ZvYHdN$ z8D(w&0lP+~iX%>VKhgqQLrDcdtwX(;Xe+84G0zN_7C-L`&pt*K*8mLq8*YwGJ?y#n zysxhLad?qKO0!1~y7` z{7C}ZwKdf3aJQ(h3EV~7?Nb0Bd`Vc|0D+25K((LrH=z8?5?b$#LHFOP%0?9&bDNRi z;1~H}BxSVet!EqQ2JmN@WRoTtSMPV6ERQiqJ3)Jye z2M)T#4&tLN&T@daBEo0qv=OCq4L;-7i65Jt3Sb}fo1gV|D-9W69YmmO_8PwOCnZ2a zY@ngE37H_x)J~+NqlnOrWa;_}erP#CC4ORG0DM#co$l+Fa@w(Az5q!@uWm$R)gHZs zLPJM@;kh+wkJXC+{`E9&;>kVjy#}LocvhRekblK7_w%AfwH)*Cse0CEw*_~G2#xN0 zF#$mSUzs38wi*o~BCtnZjwJuzvzgt?v9bOiKBb@h8IYLrV$$;xvv=)AQ!=b$f`XV( VCge}XLlWeJO)*=dzuCx_{~MpwAmji5 literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/polygon-self-intersect-expected.png b/tests/regression/throwntogethertest/polygon-self-intersect-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..81eb514a80499e51d0220fdfae2a09dfabbe3f5e GIT binary patch literal 1810 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&t&wwUqN(1_rjho-U3d6^w81Ix;dS2sCWC z{Lhznp>0a{f%f;izVk5HMl-Q_7%?qb#K6GJ zu;2(-7$nQX&>+Oj02Ch8G#U`#^g{a-k+9$-TCxPDR-m=-B^ey1FfvF06FS2IU~(Q! zpQ9NSR5Xp2SfIjr$OnX+b1&P=02AQ|V0|V~;u=xnlb@0btn?U+3=9noEDbD{JZokH P$}xDl`njxgN@xNAEQC~E literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/polygon-tests-expected.png b/tests/regression/throwntogethertest/polygon-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..c9cdb36d88ed5e40d4ee0af689f9ccd24eedb1de GIT binary patch literal 4769 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 E1%Jxwi2wiq literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/polygon8-expected.png b/tests/regression/throwntogethertest/polygon8-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..6f670ba632159c12efae835bcdc73cfbdc130eff GIT binary patch literal 3546 zcmeHKX;f3!7Tz}rA;Bn$1Qp9$%1f#!R?s*gpaM66B31Jc z2U?IJQWQ&Nh_z0QKyWH*DaELul;K$sVuFO4yg=k8?eCiQQq2!>2_{*05Cmr&FXak1RN58Vi^v*t!lvS1(B;)Y{)`J`#7odCo6v*KUKfh zo4LcrkeK#tW+Ja;a27NoFmMm0@oJ-2Y^!) zE24N=C~=(-u%K!ZZ~@lLvm(YB017}P2`uPKCjbN(N3BT2BLD+nz9U$WLlgiBMDstO zl~fP`6t4!r(O!?s5zm=6^xWgV; zHr#}7G<`t{@Xk$+v~KRzzdZ_ya}9fS!)Fh@l+BC**UPaI)3^9OWqDKxH}8#Iws+S< zZ;&XSgciJ(Az zM{xvtQtwj;vK3qD*v;Ci4GybdjRzLCtNT6q!Ca_s`z``)tn+D!BEi@GTXnOpCXJ*M?=p75&3LMf;Fyd$_fvW)vbFo6n8clg4Zq#o(HQ-_N^2 z#^YxXJ;_-=xa>9Ub6) z+elOV`nWj=NipKHQ{Ix7MPP=6qe@&D3XiJbx?&Aw4JfV{5M6F0k^PJc!8FNoCYK;v5RUHn5MKmoUv_!0)b<{ZP~%Zu=i{P{ zCTQn`qvbJ{ zYpZ@t8k=1h*wggYYm8bIwQngrS{pB5eXZVW>RT9HB;sH9Fao zh~4sj!;EMu_n+I9ocB}dSV~b*)3NMVwN(@SpSuuvKLpmJZ7&)PPxB$s?4?FC4keac zz%!J&A-+mKHD`Z%c0!NkqJ&E`-%@Io?ycjnRY?u7F`4QQlAr5qcINYC%; z8c(oU?Ihaw=~is4qSCzxdtPoi)J&*a4R+%iy>!XamVI0Y2v>Qj`66pVi$oWf?#UxO zGY={8=o6Sd=Y)>%%%du0fEvE*V=IG2kEea2znco3p5S?{QQx{h<50+bPh5;xK-z!k z*L&2}R%JO4J|`)R1EEV>-1>0Be55mBJ1h+R`>d(TU0bYdj3uHuwriOS%Qu5nj?i`TbHz-fSB8xHHvV za@iEk#_B#{+&$f2Ay-K!$KyCl`n>1NoQP@a?{M2=s-+9aM{b!?2hz7RPbIOi-o3on zTRYsC?~27c!K*aZtl9raEmYF`dbb&`e<57jyl~13wG4ZAkvuFDZ3(xLU(-VI(*$9``$lkGk(~UjsPd`+tzsvS91Y#M%igm-n zfzaEpHQ7TR+l-n1fspCW;w@+^-oZWTFErY!8M>SNW;r%hEPlt1^&(1s{%WHszLpQk z(2}h2H`h(R{j3&c&Zrn%P7G%IutX*NP$ef3ZxyckPu=c#fCXNpX>L#U8VZ)hw?8a5 z1ugw0#{@&M9D;UcrdP3eT=r-Ac4NdYT%nAD;TuaP%_Z@92SU;J zxq438Q08sjtU(2}O7&FU+i6b^L6sX=vd=g6>#k&2L)EtxI&ENayJA{FdyvJgjzLe7 z1o?Cx!A@YD<_)twiKV$mVqtL%*>$cm5bB?Ym3EER3@e= z0!2nQ=-L0f|~3f$m$bN=YJ}>!JCsI2glGdqKe|L}~ws!tF{HOvP{rifQeNkg{C- zo;XD_h{P1*^BG7xUUyN{7|wGNql89#kZtf+o&cE_3r|S;krN~f*fE??9|+(PaJxe+ zdGURnCrHG!`Vv)&hy-*dhQDGP8N|*lI9mKrJIlnBI1dF!`@O|-@?n4yUBL%b#n*zitpE6)Fr+M}4 zAjfbhkpJTYT50YLPathm=FtZdN zFX6A8<}|)Au@acM_VkI4*1o_NRbj%M5Tph#%F*FM%VZyZ$42cy{I5z&A58hb_rWe@ zmWJF9^8RYp|99UIlYJxUfbC&lKl~d54(VZu=`q6OZTRO6Sj?ax<`QPe`Pcbmd=4VR L)~;?`DaiXb;I8t& literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/polygons-expected.png b/tests/regression/throwntogethertest/polygons-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..6252519a97417c4e58f88ada4898ceefa169ce0f GIT binary patch literal 3239 zcmeHKX;c&E8h$5p>I* z5h{`h9#AWY$K6mfRxngS$cDwq+?6u{?nO`z8#lhT4v`xzc_P7cl!;y!1bAa{mt(O<~p$EyKQ~gpOre) z_3G6l22q}x#hAd`53tP{00Z9c4~UFr01se_6O1572>=VoDSx5)wm<^pF91M@@WH?l zV86`=WaBl$mT<;k1RY@k0TAx-0r83eYycLqU<7??HZr_VWBj-cvX1U?R}Gn4(|LCe z>Gk_QYY^!M`yZ%Y?skL{{d+^F2QG~Qz9kWDM%m{>d#w`$veNCqKR>%t7shskV<%ob zOLb!I@C1%hfOcQosncRrE{6w-g1`~bHXhTQzT`5@7H9%7>RAtVghfjBSf4ZiCd&{H zrzKKrS>$0Z{b(09weSPj(dCQbNV>!yf z=RJLe1YA!tA3rmcKHzYr`94d=7OOqmm6#@0tj=jyj=}hrqj4Q{zslOJ?^KD*1LX;C zwFB;dPPV*4$GY-VTd|?q+NWBVKHT=Mt`W&veeK!$vun~-2RJsQVdpn_=Z1c6&`d;S z+UY}+Ha$*M_+Xsgxm0dt@YTND5I060GdF7ScUoPT10rAeYoB^De##Tx)MLHYK5>1f z;9?m_2cd-cqeI>Gy3rJb#eHl3uCdR3AsfXTJ+&)KIpYi?<&~=Kk2wvas77O1hYJ%s zroUv&>C*Pki1(U|8*%E0(N-J0by*VspAa9I)|o51Clzq!L1bBNJMB}cm(_!4&20Gq zSAZw6sN|%93mI<8i4~)1oLXkA`8=)D)hXwXx8q^-;?~}Ig^d~|#QPsUGc@UFCi@3!iyP*x?f*U6A0HY`y%#!aTZy!NSFKo_b!edV z?w_s0gFgroZNKQZ7VdoXqVB}~r+xa&B`~JCS@GbP;kGUpbOvD{B0cg%ZN>yV=E!C3_+7@R8KzobieaI=g&!nYH2&ob33yM=p`bEOQLYp!h(< z#-Ex`gxD6lB$_V0c=nqmJ%IiNQD~}U6=n+Qh=V;kvn!d$+*srj^2B-igN^`kskNdX zwIn|G;?oSpe00$|9k`@lk!Ejcm5vh(t~-60J|T0@9F$$@_FH93T_1k~N{cA-%EVgK2)r9i z8@tM_nM@wDPHgL2#HSnUvG$8WT<~h&?@u@OqGof@O?bK0!xOf%sU2$~q8}b!6{$)9 zIVAl1B?iT;2UnD61UzPRXsXxXL@??k zP^Q5AD+!-&w5W=k3>@JoG z7lMQb205fvunyRgY$Cl#V~@2TU{ke4rf7x&WgMbk_oS+~Jm?4>9LM1Qlwuc$=*Cv* z9Ldm;1i#4Y50H9=G{4TWxb&!kuw!93W&Vtq$t86q)-D!-YrZsg+G490;(WSFHw|08 z7;lnUMv-=C2T4^IP=Lv&O1GZT=FX+evD?8c!8wK9E|BKI+;h$Bun@#Nrn$xJAc5(_*JSzG_AT@ z=BOyB@E`-eW1X&CAr;CgD?W0Dz{wP26Z90H2BO|IA0SWE7+P) z6FHnnJ3&GeemTXG>4w6j!RTB5LlZGe8hl|a)Os@)mu_K^cjTJc#?~t=;PPqYMBee3 zI|Z~2c;;_iQMzUCROnrduUTa|9t!!GQJ236MElwC&1O$%4##f^>9ZctMEF;!&$GzO za>{J9!!+AX>=INr!__XXtA)B_Rt>+23btBvay9cXjh@|nm#lfK)2iY$PMC%nn}U*N zU>a9rGKGpN@}>!87H&51kk)7hq2F`oCeh=I$kWcNZag|Apenf+(}tp*Ed8F{QZ z_Z+d8ih&b<05z|g0!?JMcMdS1dB~Uo&L^%x{dTbZH6j zwFgmCCn&im#vfc+q0TTyWv40`9$5qZt--T|iW}y_PzYc5C89JFdBhY{O1|mM;Gp4a zurcRF0UF9D1M~3?7a42hgiFf*HogSJ!7A^M&;P&L@!Rgb26dL$x$y)5IBp1w+aOJq fC!;?R5b^~Aejs0Tv?~Wk?_g0_(19f? literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/polyhedron-tests-expected.png b/tests/regression/throwntogethertest/polyhedron-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..95ffeaa52ff8ca90a4c71089e3c4f12e01e794b2 GIT binary patch literal 6905 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 literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/projection-tests-expected.png b/tests/regression/throwntogethertest/projection-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..7bcb8880a7885cafb96c31a5f6b609bc7f1e71bb GIT binary patch literal 5062 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 literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/root-modifier-expected.png b/tests/regression/throwntogethertest/root-modifier-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..4efc5adc33afe29f53200198679de55391d5b1a6 GIT binary patch literal 2954 zcmc(hdsGuw9>;$fMmoSrO%Qp6B2gZp_V_>q1St%NAgGYzvZW#h6jVeaicoOTGCX`% zYHdMbL5Q{})dIGP+CpWho{Cg)6;LFw#MK2s4DpGg#JzVgtXS;!zx``|XXebkzsKkM zy?2K+$e(X0G6VpAz&zhY00asNpztGAG^Y%}$T`5*J0yj7ue~NVYKPUO>D*AP1^DqYibm`C@;q-Q=|A);-0K-u90Xyc*i00}g}yh#8k z66XL=00Bc$mN5Vi#5({`#7GGc1K0|=NDv4>0%J=6j(iM&0rT!V#a>TEYCfE@5RBP0(Xb?0$Ebw6wOfi6ZE&>fY}wrd~JAApdZ~5&dGs zgR=9~rit8K=r<-LSUtw>?m}#gH z^gRRJAbP`&uGT}Nsh_VKnHZp{h#M~&G!4uES8~@VAZ&LjEVS^3?HkaBq<+C)Y|a@o z;gW*0`p*;AZR*%48Hc6i{}EUw^3Mu6cUI z5MW}U^KRb0NL}D^@vUwTU({bX1lf&x={9TCCFu)ALjG3(fLeiNs!}edxlc` z9hOqZ#%J|ZZx;7=Y0akeMo&#KOdKhnu+Nj!MuYX>ZOC54s{bX zCL`wyZm|iK;YzHIu(xZ)5wj@<>4rgBmpcAkm&1F#A!g=JPL{M-w}q_@gSCHt+Cuh=^*UBgBMC(7cg5Ja>+kC}D$LcTDD0#Xzgl+A#~t9|h^ zuc+sD*vt$oV<?DFn#3@ls zLGeyY}$GXl`|VZE0)@2`-(w9mZst(_3m>#;6wBh?R&vUCquv*gcjWOxnrv_3$`dZmkK z+Aj980n{|@zX3iJELl?C@xlg4-{YN@@vje(SQ;*~Ouxz*YR^H;xx@fM*u*ojZ24PN z!I|d>f1b4KS0fwE2Lx-?p3g^9DxIP;ai$9k4%vDox11sc2}+TLy`2fWgM2K~ay09) z;iklQ__}-33Ua4d=QRRQ1o_?CB0N1GP6sEi)GPh)wOU?Ty7JY2TV7AUDX`|cBe6tx zTChR5Y#t93i8@O$Ioy1Lha9v7!+-X|Vm-AD3ebpzr}u$|L6VckbD5UrJ-L!DjFgZN z$nlo(U+{-p;?a@N4o8N(7@;)&IzkCOHRP!04%2E8)PTNg;zG}N@h7i6=H^U{6~cr> z3`SGls3+gCX=N+*9LBw|UUgl9JoCdmq)o*vd@9npk5jFXoWWzE4vkkf*mNzXI)*(< zGX6=cT##86-Q>hkIJ#wFxsG35jfq9F%QZNfOt;ok4LlCq4PAOVk( z*=sbWhaTn%9nfLU9pTK*7;7tYo@}U5zcWHi&08)58bjnM2qD)-EK(zG1dK?OZRV#6 zAXro6SE$|6aigK-z>BO2TWA>rI_mJgc6w>501pol$obb zF#u(ELmW6GHmtwC{|glt3@u>@L;);nm$M1-+*8ecNuEEOXPaG9E#}bDEI^6~jKP0* z8ADO99?E(K`tGM1Lo#M~vA=RUXZH0p#7Xt>q7u3?VviWtIwrHa^0GzSg_Vote1Lo;P@PZ4RWIQgr|KnA6mXu>en%MgXf-D z$g102R0C_AdUrMkd;c?^@o95mmaZFo|8tN=>qV=Q!qW`xz+{15QM`M^kJ{dJkXECA zStk^GE6Lsqa=r2z?bCzJaW4|1cBc+@S9ENC(JbiQ0vc1k>hs{k)=vVUr9!FS7RH}) zQ~j`$WKLh^{b$;A%@ki&LMazHvmnXA~GcucE3ANYb+dlGQ@Q3;tX zSuPC=^0JdaNbNrJkOO4I<_*u9U5hNni@~Ax=-QuG`6L-Y^CPYGz5f2R6@z`=!j6BG z`GcajwWccN!DSC)gKb6cakn#|9yO4VkL~6Eb9HP=<29V5>HGIBWdI;ZocnR&g81ka f=&}tnraL-Lcbh)zo6byq^bG>$2KkozL}vU4YV%t- literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/rotate_extrude-tests-expected.png b/tests/regression/throwntogethertest/rotate_extrude-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..94b0d9c62b4bca2c82a58c1a274c1e996e3a249d GIT binary patch literal 6429 zcmd^EXH-+qx1A6S1c_jft^^f9#h(P}3M3*bNVOupgLDL>hXkoA2#SJ$QUpOoDT303 zM6n=JjRqt%rAsfN1d_ZP|F5m}-naMheY%r7`<%VcnVGxp-ML|IdKx7lB>(_G85-!H z1Aqq_@&JDBB|_@NO8_LA4fT&-2!+k`>)al>a(&Qo@nl#4U9#ElJl;OvwXE!)q=LQE zIkL*7U}if_asBSg77;nA1C^}%Ngg4#=PX64nLixxh0Ev*EbamlcCV7)e3=qT1>`w*wpsHwReo&|{EewXz)v+0Xqz9I!) z0Da%$r;z}gtv+%bkdnWgILHf3S2p~Jo10(F;vz5m2m_LJFSHw~s~i=0fSj>3O&H*% zp#>I%?#{Q(ky#5%N;oYcoBL^eF!11Dr8;*Lal;5F>G4b=dyw478vjb%z#T?Y-nIKQ zrGUOpcst|YDca%tj^&|Pun9DrbZBW_6WBTd;o3=`2jV~(7<8LRr}GWP%YZ`HN)UA8 zczP6PCMB1+{v`~+gkv854B!857A6cQY3`CD9aaXK-5#)-d_E9|zzc$HV$PuM!_c4A zLH62uM(#sC2gM4$JR?DHiOH(Ql@d)40F7nfFgp>lZzsehaYU7Ao5>3SNKPg{StWw- z&etJ=1MQ3s^5#em+*H+Tx&DX{zI#9!BoddmqDV5pknXZv--5ztNPw>1$kj65p>zo# znG%%&lD5NTE4##j4tVUpIT3=S=sAHy!LzgWNDATuFmPS2=XG#)0w1)QJb8Q+yFD1z zVVLG^3H&E?FG~@onIJ)effG;?g#Siqw;RPMaydYR(AL0}SMryt0?69nr z0Vv2EUE(#grmepKXp#FrJeiWbsv~3p-e#M%Ph8?HfInN*>KifS{O(tUBy@^S5znAa zh0aM+w+n#Ooc!88C*$n(lsG;P7I{bkq}CPr9UiuBeH|toGlK+$jT?+uh3*Qa&u`@f zpO^}cK?Sy7lXd^cwt;}ywsvnRe11NGAILom**d2|aG9WA^ov;HpOR4&a-N4Dd?&~Q zo*5H=R5MQCo^71+@9Im@4jf&1@$CH~dR_rWYDSq5Lw?iSF}<-FzQ@t>_Z>DBM$sUV z)>BTnFu3m*8%h13j-)zcFY!nk412=6b)+qq-c&mIxDTE_V{X=lf1usMSJw6dy__0t z%>(XuWcj5i`;2qO+X)Sx|GaN$5K|ye2?=aG-@z z+Wh>avt;O6+9nd{tFVk{fL2{yN;8ERgsrhHuFRQh^Jsz$t zx?6watUFszhokc+1-`ma=J=keK^?9N6Z{mvxpJe;A1;F-)=%^afL7IzK@52)w)!o* z!d;-frD^*Vk@Ycn9#gEE$LZdt@{+d!<1&w(6DQ{(_yN+FwbotvDA<5-I&6I|;bl*~ zfKhvER$HBF-lGLy1=ocxWwn%)+RV_+QFT1%ai(S^YY>VvJQb?)J$(7(cD_z*YvwKU zqg2_V&fi{K(^oyvt+I0@ut7)Z4@90KxjcQ)A?7FM)_erSa zIXn8iWt<2|&(>gGRISHN$tFb1`=oV$7<$Flh)-WHNAzvha0q+JN+LKmTuUS7?>-Gb z&GB4Oszyc))h*S~GpB|>=h@5LB1byD%Xl@jVVKS6#wG0vtL)0Y_bcr@c4YEvnd$pn z`x3VTrAze6!Y@y8QK^kT*WVa-2a28&dsEwAl}%QMledR=cw7syvUoUw)h)Q=ve&zM z^))&b=xJ$3-o(+y_X%k_L=Yhfvl26_2ldy{`cUS1HnO0sr|WbX7p$D4Kc+vD;B{y%#4 z8rO2Yb0;|~xsQC;-ngTjyS8oC_Wph_Melz5g4(Yd*N)pJO{B&A^5WCI7`TcU`@28mHqT_w6v4fJH+xTkS%aWcbynfqzP5oz(-6;{P7Sd(?bh?j(Xl4Ircc{L(C zfFCDQjVanl^k#9ialbZ(*_x+yBO1mY7R{bN8P+(%{-`|F=X+F2B&Bv>xgov5sy~oY zfC{ZrJD>9-C`W5UP@OOrGScwMI289SnB8zBAmk0T!{hOFwJJ8B+K|J{{P585vk0M5 ziwDd1A1=NaV^y>;F7~dPo%54gy8l^hw*4d_s-voCIivo;wIr+Q24|H(3Gch zI-_h5lSE(Gk$k9qM9D3bXk3RJz*x(89{W|n)@SHuRW=&`?CU?vJ7mupCVvhK&S~7t z`n=4kW&a4Px6f!`SK1$J-3V_F53iI(c%?VkuJ$Ze`O2js^tY2ZOg~P(Q}IUCu7>mU z6TbJ3wlQ)8qkU_H@ej0Ijl!{4e`?@tHIy0$BSgDMX7Wdzg=oZAo$DM=S~59m zl`eT&GgO9#!S8@&2X1-)tz)oNtJ2z*>_T>&GV846%^PC)SynmRK@f%W${t8paVal_!6(VwbN(D0Z)R zKO3JWnVBa?-u7va2%PR;pVyLZ3`kpF)p_vNIKJLCsDx%cuq3qN-E!znNQx$Tx3ux< zz!Y(BkW575O0%G$1BUXwVN24NYlt`TQfSM+wX(MTsBTdriBaV%ST)EZT63&`r(yIoI1B zE7__9QLddC92_H2a|*F6??-`$A_m2F(@PAbvfL8p=#--x#e~{w5nBITh6>e+bLFt> zgGiH^hayc$Po&%MY0C|2?wY!h=cs1Eys{TfsM8yWCdsE`F0w0hVij*aLFb4T(Gukt z8j0((Yg#5eH!$yDRy#*D*z||7Hkv{?O`R6<%TXr5N5A`Y%pdnd2h=GR=+5OwVv7+@HCp}}LtZr_2ELab`%bEf+htcg88I4E`WzG%dt^}u>SsR`Wo?h? zeEFRo_@}G-%IbRh%EK?Ak40|&?A~w6RL4{pZY(uulLhd0z3r#wp0n4AsEt#^mApBN zEY@x9$U){)a;AwGs;6AUJwYz1@r264c!m9WSaZ80swPV;#&gJ8r1W&ZZd8e+J<}~g zsK+-_Z}zrk4Y7bGn2>L{2uI=rsvW5PZSr>!%f5qE>t6CZW?EDHm&|4+$nC8A)>2qo zO~;@mD^5#)`pcCrvK^-W`G(Bd%!p8pj?BedOS%EWW641WPUK*2Eyo_{_WNCfNsX3f zOzgc;oxL$7l{%~8bA^df*ezEjCx?l7Hazg@PV{aDEyck5C+U=&JU<~NQwh8HJdmhN z3)Bx^WoXY(awBpj&5(s32Ex+5k~%3(t2yi&b-_1)eJu;#Yy z78e9BdAjNRV9wR8G2Ez};VJRfE$&B`AA3^1j5wB>h;yox>?#tBFLYl^m^-_DI@s-f z0!J?RyGz)Ky1McLqmN~SRY8y0ifD(J%m%47BfYOA!9%Q9!n+11EdQjWNSmw_v(l{ddg88JYBZ;> z`dsHdm5aH4-hA=(;M!t!h>(ou9>PCoE_a!g+Wjikm^(bI{GHT$L!na1{4OskN4oas zMq96zS!o9Q_sPu)5B1DD^30CR^n`JhACttes0OwrxQo{Nv2#I3=xp+tsrP0!B}4kt z&!RXdvfG}CflonxOP?n5%&(1i_?YI4f+BRx-uCGl`Qn8eKN*NJ9Q$qKhG8!+$w%G@ z?Ut=J+V)22b4~c75LF&LL*DWAx%|ee!ef=wq9oyYt!+j=F%HO$H-O#Iwh}+8sR-M+ugI&nkY_RB z$NPy&n}N!zsiHw||C-!kXg|-fyd}?lN~~S&ojbGuR0{_yc->PJ6I^W`U6rAK9#%jZ1e| zqSa}+ugFryjd`UWs(=2m`@pP})=2;Q$L+?Z)6u!UuS+G_FN_Z_gvrlD%{;#jaic&bM=WcT~o?rr}aA6VP(s^EESyxHUTmDyUJvYJ~mH+n^n1I?VNsNd@^$v7+V z>&|br$x_$bvr6(o;>>1?PLq+$*9*F;Tf2FB#TBLOKT*{}~iDO!ya(B$x6oY-2 ze;;8M<=(3ev8*6I_d>xj(t-*iPX4?&v)kdEhC`43+^7tNpKa) zTcwZf0QU_4U(=$Yk`FM$Ihq$jYo#ga52XY!wLG3aqtto`r5 z|ED$P0_}{8kcYSTfAhRXhDX=0G1IpEb=dyHO#5%%n>>`;UB~_}mw#$;hy5d7$dy|{ zLWatF+~%nL!!0D9xN43E!2f}e8N7Hn5ZF;l{02ymV`uGELEg?^V{?9}R2T^j_irPe zho@F!mxZJNbXoL+F0oLFTgalHIF@h_AS$DK0uEm7DAJyM2|EH(GqJ2^5GD;f^-~I% zUjeE>R@(91+!cV==900TF0Xh&^L6EG8fqY~6WB<}!odxJDSJIQIJZMROcVw7tAhIo zP$=)cW+esu%OSRpWa&3LJ5a#jWY4DPAU9$kVh9S@Bfb*hfdYw^J&Ye}K-S7}PNNet z6yfF`fT9c0ISWyqK%#{2`;TNp0AcEg;=cqN@ z1>}7IHdg>88!FYrPN9%dp#~fjch~Nv0g_4V4k0UOfl&A-Fi;W~B*fzCE5B^Ofy{xn3OrDBSu#`w%>!STrsB)Q0*@m#^Bh>WxP17m6+6!JSS~8PdJ^ZrD$(OHC-{*B z!NM^&rqUTC<)bnxl)3quCozI`Yb*Ym0USC$Rp0~y* zyZJZk&UP!#W4ONult=n8SeVu>os1&3$Nc$-*6-7zSY_UcG{u*+*U57G2syvj@T6fO0W*3NpzwQGj+U z5JH3+z)5OU%nZ|l2OEa~TR&*Cc(7$qJB-CS+?cP~RC#=ZgB6o|YAAnzqqXN}!?>Xr9f4_(=%-ubbT7KYk2BQsltZFo=z! zEMQNBC2GJ$%M~CHzDgAYFRHU>kgDHLVCA2g$B-6~mxv;vAjVFx{|PnznN>N#BhH+?maD*ylh literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/sphere-tests-expected.png b/tests/regression/throwntogethertest/sphere-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..4792668aca059ff0d865a5c063116098455a691c GIT binary patch literal 13794 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 literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/square-tests-expected.png b/tests/regression/throwntogethertest/square-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..06aae6c1c93c29d7a65c83d897d996b81127c11d GIT binary patch literal 3962 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 Cb$^op literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/transform-tests-expected.png b/tests/regression/throwntogethertest/transform-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..a120581c7448bf418fae110294d516dd6fec09ab GIT binary patch literal 14734 zcmeIZS5%W**fp9E3>FXt6={M6!Gd%mAVsC9h{S>*9jVenkq$}F4TuN`N|UN6h|-ka z64(^!MS2ar7lA-}_*dNDW*!8hq;y;J`XgWH+IWzg$wJU1?WK<_T;u1NCo;w79-O&w2OWMq zjDIA?NWsyV;9Xs{Q|yH(ikwj%FCMQZ`^!!|`|v!lCTUF%WiR`z$R(?Kyx7yHB;HOL zS7DcIGn#9Vbf?(WS}8oH`;s3oGu&`+Q z4Uq`QDi=HC3%?&p_!HF7-1qGlh=4FuO&{Y>heFZS~fekANob3DZOk^Y1|IgxRc@`UaE!tQJ4h|LY>Ya|LS`qbf6U$S8C zfo`L~u4~)!-;pS0e;0{CMFr-nMZ4^D;EgQ?1J$CtGCOKYPVDt)J726Q|Ga0pEVXfp zDZMA$txrC8H+k&J%$@bd1GOek1gb{N7pQ-hPdsBr@&YW`E>aj+Z}L8bXTd zz0k!!p)YzM?ozUU;xKP4{HCsRofn}^{=k!?#nGF@qw#Dz^<6xDaah7x#HXVKLZAzU zNE4h)j21Z%wBh(bK0|Vum#aJ=rUDu%I55>XXW-u#1j#JrhK?$7iYn_6?&7TIw6 zy+dU9AR^%O@sRH+%U?On<1^6JbUN&*b|T62s){L!WB6FB*tcvIiFI1QP2*DNw7I;- zSOol4vIDlfM(tho*1s#<))%ZamM+D|dTsBuV*NU;aJ3ZFB|HSP?dDt~goIMJTujPXiV{C8}>7 z3c9)Buelim-v1r{7isJy1~nCu4xzu!xz|j3$5aAVwsg zWK5se#+M)Wn_)&_$Baz$BjABk_%Q)>^<1ce4rKY?wl4J}e*zAws>r;nb}L2=?vy8DaE8$dSaTeVki4$ia3!Jy5%Fl)%}zIt)Mmxw-xOk z6AY&&&o1))lA6cKP#e)o)RO8Yiw0&?yPz$My$3t={D!7=_b!r62Wl@`xi)x;Wq9rx* z7?(x|`N<1^;Olc$mkG~V%RkFI?>D6tm9613L?yBfz;`=0Mgn`Gj?tje2`rz+Rl zG+{U&g=eg;lM`?wX}s&f&IFzA!M5{CUGpVt6wcuedF|k@fqc%nzqYtYYOau)i)O}V z?J=WjO%Q`1G^px|^8%us&>rlV=Rz3wDz}h;U$`#JU@kAZ9yvI)h?Gwei~LdfS=Fu>5ZF~yz zDN(T3;tg}Um(U*TEt#ypeUpVR0;20$u&2G@QhW8S5ebIBy0|@qGrrL;*R3x*`%w8B zvSdAr{-~zuL>Q!&gE8L{$^EF0enG)5behM2+Hny{Z3pLuCbSQg<{WV%RNjqmG- zee9M6OU_+-yk`2NILkk7%|Gp*p7)zd{%$<}_T_`crNbk*jB5C+(RR~2HW?$oq!w^O z43Bj|{8#d#lq{nrODBZ+q@EcyR(9-JEr^{KT)x(YJp-zFR;}V_a%*H8MfdDZU@fEO z<4-}$7OaCtoUEu0p02z-mXJx1Wt1_5r(6v6VwO;wGwIHa84Am7nClvKr(3XMXE>__+&7-W8pfoa z5fkJYZAJLANV0yJqUG-Ng?!=i#x5TD_@PJB`ONb_WH8TWJ4vRw->p}c4¥f|8>C zj*Q>Gv5F6G*ud6LImjYFN}C<9@h-LDVfgzki-_#TYoxiwvg!$0YU*9g#;$N>+La)# zzeTGE02zwcQJl;Ve(Q{t_d%0q3(V$?HRh@>*X5rS6n-7*In&jW_)1$=DoSrYtG3~; zhrgnj0FoQuD0`cWQFEGAhHZy5VV48UN9n<_e&%hQrhZd(bB6i}&wZb+M~uS6{om;G zayYBm$xtO(d2M6mRhBVcg126QXu@Vg9k63ZxSCz=ii6ySkCG`;`hg$_jUFAG4#6ZP4tlNVqr#;pal&K}F#IC(R z$mYsklaPp(zw^#=Jx@UZdk`cpsl!RSUsCQuGJGhZ7tBnrY zYP1Jg6WdhdSr9k}KB;(FZ*Smh7u^l>YhaW3oI^;Fg;IRSTD2kG*X^s+1v<^>m1z~# zGv5U%QYs$OiFmnq;iw-uW3P^JIph-1^jCU3-p<(g4(ty}0ZSW8N`G$?+Il@fhI%Q9 zSvsq3q8P??1kruUd^34hary0+p*bQ)el6^&RGY%gtiEy<1$j!F<8 zeS|v&8|9%Zs0YTwdK~SBZ*UoeHykb-*rs;Kncq}Knz?_v( zSS2vA3hYgMcl?)0m@>2VVqu1{H+>k%$;7l!OT+GF8B zjp(shCKg1dt!u=UZIVXW5V{)|od;X`l+?Fl3^$BeThvK_5z6&#=|U z6m*nJl|U)rvl0`Bck69o`JG=*(dOdq)d#FrrQ}%^D@A-1v$T=d*$?{uZCT zZ3}ommJgG4tAKe7F&mVR+N*3`9XzQTctU0&c6RVRCBh{TpqM z^dm*dFTFrlISls_1TQY}rXXrpc8mo?(Kk}-+L`4I8LcQkmmfN zSQ`uIMpAH6#rtL~`ME<)p+((ZrQKopQjw?bhWkN$<0j!wp_>lO77oiJl=O@=nmAzMG)VNy6=iRp$ z-Y_%KS8>L+V;yQ&8y_l=SJM5beeJ6VfxpeKsr83$g5-K3~kCl^U6+E#Yj17o!GK8~TD zMN(f^=Vq%U;*0PI8`?BjeiR&P2hEM+%KFotUzyzQC72K?7rkn>K$S}iNTbo^{`}-3 z`;8T6QZk6vZqq@!&X5*+#&Sd4-aD#d5%Ycv(M!xm4fp-Ew*tU^bAfeWo31p7r>r$Yk?>{>EM z00(T+4wKDBPc$M=yGtthkC(`KRe_29@tb1Cg;^QbdvH=zFaF%%{DZAs>fP3@_YOr4 z*lKBK*>rU+>hk}jsT$4GPkU__RWg< z`__2AJ_c1c>_flxwGUpsl4j?AiDrrZ)^E{*@?$z(n%*5Bd{^3a;u*HR_N=;yx06Ae z11P6UT)rEBgi9CbF)!=t>XY{572_;r`?%SVUI2pgqGJ<~>(*x#MVw5{c;%9uCa&2E zMAgHaD=bVcRw4xL7GJ8pmW@ zvP>xkJ1n2N=Zha2klpehJs&&0@te| z*SawuS;n@{W6&Xd16fd~m1p8yg*FOrY6zHAAI9&@Y(eQ_J#d>bYku*K?oq&AFt*hS^Om#W!pBVXrX&u6??RVU}? zUyD@b*fXsxTyQdqNfN~OCuOz~1KvS;K#{PmUID0Ypo3A1mJa zVMRxwf~={*whc&(=(PK_a==J}a?h_01sp$$WC8ya6OAQ0|5 z>zO|LJLR?E2bWZiU1Rw|N^md5Y|4oza#uw_%AK8`-!kXyDJ-~m_}Npt73FmNkH=pC z$SG56RLARX=$TwFas#6hg!yYVw9z0FDiTPI zqw~F5!W3R2nQi@(U|*G=?Q^Z!q2X8gR}S`!P0mw?lSGX3zYNU|cnK!o8AAUHhS$O9 z)2UbXKsc^%*MEpa=Mk4sZMRu#$HAOJR`sdm6((~PrZV+a-A!Gem(tqIZ@cHeen|Xx z*y@TSTlXJe@iOJlc{>k7U)P_eDw+MktWU{Z+N0qE64uZ?5OIFDGN|i?9B65;#Ud+9 z2Dvvk4YK!g-R(Z@?#Ctr8O1PAe)SzU3?04s9*kn<5NLZ_JVG(9a9kZ@od-YkyZ@Ow z8<5wY5)q3;gnjL8z{N9=u~&X|U6w#zJZb7NBkJ zKQo=PbvTQugW=bNx4MEA0`N|wcJpgu0)Iqse?FAz+C;LC@|q9!&IPRk#W9)`PvlFOdUPU};m~k(eFN zDGO!c6M|d+fKf0PG&wni{OEbSL=!UhXm@AwUqhW%`Z7h(Kt$pxanOUsxm$?pvMZtUHKwL&OayHQxk80ux|y~Ulwi}#BXLAGnemZ?t&){rOO%|%`w1`Sy}jcGXVrZ^U%YUdVoqHf%h z&*RuSekguS5&2a@FY#waC@qms>IrnxS|4{EvC+bl=}dQ2s_DwL14Rm=wU)z{7%~ zeOXXJK@;&j!#~ae-n&gB$?8Vi32wW?5qe^8dU;B&LoW6YyVksbq4iUROq^w{Dr#TX zP)GR7aE5P~0{VL+1ui-K5}D9=&RN7H^%sg;^n}`s>R@{_@p%?`_$nGU^p!p7` zeFwu-&cHDgQVs~G!OnKH5N9PbiC3$O#W?AEx<{~;ii*m8!|PH^ z0W8B;GYR7w8X5u^(#Q2A0;zF1>k_sX_hH-VQ-dFOMLuAJP<(@!>e@J>nlanF>>1`@IFQ;=Nr^wqYm?x895U4J%8b-^{1PSX z|6}DBMNmZ}VDrh}^B(1)|L)R$v-UvlPj|DhmaM4RRh8V2t@|}vKv)IV6RqDJSV_zI zXYJCQP>Xq%eM;aK%b>U@zaTT(Aa~io)YHdOuu_^9>@px3()JnOxC(_CA-@^2o`yE^)m{T|dfN2H>jKwu^tqSb z@0vX%m8@_>vqGS)9@8b4o|k0(P7_;aumJHrsq z0|!-;J2dXNcC}}x(I@r!`J3M`TgFh7HGMOyz9U%eFDpx-dt6#!8^e2Dj3p@mb*dEd zk06(%P|06AmkJo4(+qFfWqjtj+JaqU3Lq@$V{Rc4XXTJz*{82c@{_--FDs@odhtwL z=<@dGUc{4q3m`@fLG}Ub#%};)eh9^9`$DFH#w6nF>8^@d82k6bTA4*nS}Q?G#@aJD zufZ$1N3Q$5`kQuc4!xJT8%X3BfwGGopzYA3!63Q72NMm&QKr)Abt={7T?AbDR(F@? zgv?OjSCwv>oRq;5cg;>-Z_@9MY*R{c3qQ@u{*q#x1oOp4wd?kao$Bqmv`mw|ksrA8 z+XSEGo;CxnS_1B*4>MTjXISyG(f%HL|K;YoESR{ItfOOn8_)aJ79v4z9yOHRoX3iW zEfur76hIhQH8XA9=d6@3mA9x11mC8DcYrX|$Vs4%qbF;py0I5NHHK4-*__=sdd|BS ztrBT+h@uS~&LDL|f8m3SYVvJXY3+DkF-z5JF=0NOcCeoxsCjaxi7#C+O;DtV)mZER z@DE$$tSkYI-=QG^51OlRJ!zyA2*b~C=RHjZJ0H#L-`2tZnVtFdAzAi9s!4MoEpN1P zf2~L0%&td?faTPk12%A>_}bg>EgxkT!7KTuIGKP}l%;4t7`r1?_rXWSueRxl4%`tO z;JyD`q+0eA(9b&-_rC$|xIO7T4k(vOfElfr{tNgXUxy(CbG!|fpDC(|-3W?#XDR@!WD%4<~lE_XNU z8kk+lgu+x$hoO=x>E4-udQb>DPJpoo{QMUzD$M%4n&~CRF|cs&oxoBO?{XZ3K9X zx_eht#JtVAZaQ;ue98``+b7QEHp8(Z#q1JM*r$4vgmbY-F346H;@K?GtOf#$Nkms3 ze2hA}8>m|^;uQ9gg^!Jp-?ex&F%k!w*@nI+Ru}Ge)BBT@gYUteWXS>H{ zkM^p^6z5O;6tgZ!^ZpKu1~y;5Hiw}y655RUss(|u z?%3I2Xl>wub7Lt=+>Bu_Gt=L54>>Ad#Pdo0Lc~8Nj*~7J4QT+|=0|w#+WsWC(S52N$5eUB*b?rHcx3Uh^cqri%h;m%MJI_Nb zY6^NSA}t=7AMgf38rB7{h|BzLUx1Ps>f~k{XUw*JN_O%f+vF|#Dn=n#z2j9Y)AIMy zd6Vz<)mvQyzx{kSGe33F_`Ka zJ28p(Q1`R2xzi98aNZ%S4iLA>S6b&V{5xst#|5Y3=j}?bD7r^h(7d8mWks_v<-?<6 zvj^a1sbRf?5B2IypP^2#X(D9Z!scT%-cY0o-#PX}Q?oYE-ZL8+%?UlyoEp2o?R*1PP zb!Ex(&_3NFp+S=~CQw{W3dJn%6ST&{CuqE#t94K1J>w{EnIZn@Gy60Sh=6KZo|ouj zg+^;E0LQ)F{>iQd7(|kYcD>qmJ(iHEkUM1auie6XuLaHErH7|w7-vWMdIwApWp=Gw zc1`g!g45_{V%tc0%V)!>)yGdiwZs`PgP$t4B){W};A~D_D>?)F$qolcTym$Vh-gE3 z4c3WvWI`vGA*x4{`;CcM`q?|8s1S2eSy5EesTim`Z(I#|?9UkaRp`I;%fS)PRclNv zRDG{L`eNmBmBDPw5A1vwEy4@Rch$vpi~Z$9?8GJ6Zk`|U)=T}pc_g#1!w-@L-M;@^ zvUA0rG*XEdl{>P=v;|`vuLZ+x`~N$J-PtjPtt$z{a)EU<<-d;UH<$5*O_;l0kA!m) z|2Y_^F-8Jsvz)Uhb}OuYbOL;}(Kw2Y^(r;2)C6R3lmUPajF<=DE`$%4{1cyhVzcrx zukv42mMbts0&FHNeY&fErKc{0+Z@SCg295rDBBm<+ z6j{yQC};T$TaK`|R1J832Br;@b^Hyfk6hDzq|N<00f_9Qm!bR;QIM<0xoGWRht{tb z@@pvL8C;pV)j;H{H+KFfxu!!e&9d7G?d^U$-J~!*sLYU9`n90@S1Y`)o?D)jv3m0A z1yJ|s12)x?{9nu9$Cj_>$hbXVlSK+aC9;al8h4J)-0VEMRHp9Ln5TcjTA61zdPQb=MhL5AfcOf44div;H)!JZthfd3Ry;mG3V|1PW0MRqBQ2{qsa$jabN#%WWPVdXX&ER|! z!vA)^hRg6&c+Tmju!!EhA3E?^ z>|sPeBe5$rFiQ&npDv?)j;yU~^Zm>x=*ahMlZ~p$#f~1f5~CicKl*Egx56Ja3_F$cJEu*S@x@&RE%~B0uw>>inw)90TcCp|6#> zuFyt2w*8gz%QOSGCVcCDzn(C5$VhNR{A;-$To1Bx{UccBz>#q<9WGS;7rMRB9v=b< ztvQ`_!SRc(rH#flA<9l0J7lz)mVj5WsB?tqmxN6?7wOte*rI5=tr#a(4cg*rZ=w7ZRXs zg++Cw$YeoqBVcz~DzAx%+1j-BI6Ib-+*g-TwiRs_wF18CV|DSTGCTWX2Mwo=fwt0v z`Jr`VFQ;Xs@4OX(J$XTAInTwqd@{2W9sS;~So^?bM_zcCOtVyQd+muhSq+HL5BZFv zT4!-_A z`<;;!eZ|SD2_QY4QsdpZ%3M}guUe#jdvt*_6LHYAL}<_!2H^Q8RQFP4 z4>t7vB375PUai-(E&BRK>h%h%_m-1xc9r3rWBr8BwsLNOb@xE<7PR%1Ph6M86l%S~ zxY=DuhNnUWvl@RiSZ+m{9{A*6%e6_b>LQNV5zl2%z!ZceMu z?-)x+3#2EONB`s5t9NhDQd%yH;mwP3wOTkt164Rb5Qw6DD5W0?P~fDMb}4vrUls!= z1w8y;!{B|QEC;XDO>{z_66x(&X$rUa%C6`hVNcDBCKa?A6I^;CJ%tIF;>QHB*k|te z^3R6M$hOVM_*?T4fVeyki`jod*fr;QDl@YbpYL$I9U!dbmEd-4QV$kGLhW`)<{nr7 zMDW|wXHbS4$IaqySal1P)d|5|ia4|noICn*X;?K5$~O5TI9(7?x^fy`B}?qM+>5Wc zAm{k9uDz77@b7}uubuc;UDZ>#X}_WSk+l0?{rs$?e`)N3neMZLXw2d{1KV4|k5T#T zKXC>5op8LlDV-P~%5uxR6#{ijQi2nxTQB>Wm3!L}Km1LC#YNQTsrD0jeipgXRIkXHwey2md&6cDA1N7*koO0N-rO&h;IWH!8v2Wu_|~a}dpZKju(apY z^AD?banvt9e16`pR(h`V0r3L)fsNQ+b)L@lRJS6-F$BU^+e#W7KqoidKa<4o8KIkG zC13WoYBsCDUh%`v-fa!TdFmJ;CK)LUaY^RQ6MH(|>>3cc#>;D*b z3gcJ6ET%7QyiH*?0#3%N$x0zc6Wrs4ST03z4ehDEYdh#?VXwnRJqzs<)K&bHVUDTu}0; z6y~2AuCTLKo(w}T_XOiU3+8TgqxR+!IM~Wqtz-SzWUPqL9?vk1*dRWD#>((Rw&$iA#%_G8 zMwzmVN`LKCV|?RkN0E6{fV-49ZtU!Nv`7(7!g^6qyi{lV+(W^?_TbpqA?t5h>_}mQ z6JA3pxO=WQF0g0qc#WiEDsBGc~KooHXxzLiTe`(`2A(J26iUtEa08wjI-_A>30dQ!)ls8Jn zDW=!^Nd7|HAxEj;zU8SsGTa_-$t_DIzkWkI$Pu*p5yj5{7l>#z2&xzx_D&Er)5j^f zmoU#ciVgzZ&xikSu2M#U_FZvNz2xBbI5MzXciz3=g7l`D5Ya9q6leE=Hd5NN*J^HN z#Rfs}59&)VdTmiFLf${-UMGJmr6LU$s*>J4DVU24kW|?$srh>6}qMdBwoC?+Ob93SPjgQDt^DXL#`=FDIOTeJpTEr+s*u zX>kVf^}P%uvhBjj;*INPlDy{xQ3Z=G$q7DMHwmA%H%dg}3D{V>8au>#$kOoa&1ifz zN9SwfPHJo@nmK0ZN#un*3Xx$jqJC`=cixXV9Q605ET$@C^gWSMN+JhEQnEK%%M*kS z2jwk}4fY8#Dk9zohx}y4CoH)pBtKucL-O-MRj-UoMiZxj00q#h5&j;rQo;uEbBPz@ zqa;E#4wmaZ$%<~kJ{^!EB#yo$!a2W7-p=pVl$AuBNFe&}S*2pqKlq|4@U6~Sl;B1~~+Ds`&0d+<&QO(2sX14^jiHl}AK$o;m@ zCd3u^a;1Z>Z>pQ%zAi^S0P(hvUd#)t2Xcz!QU5{}V>8LkC{GJ(V%0y8+7jJ96oQ?n zsYbZS$a$Nmy2s#9M2B*KsZe~EM;@FlFrA`G~1oyJ6y=G`84 z&y3iAOgokYvV$q}d%{+JCFbtnYLyyuwd~2CipJ6E!u{g-%v~Iw(UG6o6{nd`cZ`YSEF3P8YmFbdFi-0^c~4Ovk;@7mHoGp%2>E zNcp%lRdC7J?m)NOUvK_VZdgQJWry_tEdN}zKVS%)^w<*-%50E9a4B9Ou&oF~70@-) z6&IxFD$x~wX-k`OX3F9JRb6e+Pz^myr~eOTS=K3yYbp< zRM&34l!=s~s)M{HG9#P2cxsP_+f%k`WcSNXqY>L3qSsZdDLI$z;%12bx}3VH>F*BiZJ2aW&}dp# z#xMGxOM6%AW7$fkY!4tmMqk@@{J3OsF-si@ko}4@BBLBs-wk~kMzFwHh+Eh;e3mye zeTlm-4LlFla)MiLk0DNXBdQN>66I zYUYBu{S&P6bqGY`J_t0VPm}eI9RfL~1%Y6}qhyb6{D1TVE+p^&(*tVYc{}g~8&)+M zynNaZfq*C3AmHy-{r+#*^nb_m|G$09p87lO;D}#-YH*OtBhR&rp5H~;*n$TxA+pjk ZGSW)YS3l3cWc?0uTkD=?{te3){|5pf%Mbtn literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/triangle-with-duplicate-vertex-expected.png b/tests/regression/throwntogethertest/triangle-with-duplicate-vertex-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..f8679d026b3243bfbc9b132d5ef31790be4fa78e GIT binary patch literal 2594 zcmeHJ`%e^C6h3!m+%CAtt1S-N?t;K-qOvS%0eP&tsMr;>QlN&G)dC_K+15u9TXrZ> zsxit#9$OHsHA{S!CML#cWGb}<5vYz2tfFgbr6VXLETSW`(>oZ_|DgWjPkZ(|-#Pa@ zzPU5IS0pa+aQAly0FQ*F#*Y99Y!aZ9e|GpUItk#`kzibqoR6OLgzbBHlU}^C(_q`? zcxT4-?7A7r_Kt|R9ci{|M_SvjU;bG&ywQB()YGH&*=1i3)E5(^f_m(Ep|&u;Ksb&5 z7`p1#%>*I%&IbU&AtS(H3N*_AC}4cXU}ic3FMwsQVC*#jO!o#EeiQU&5VBtW2Sv=w z@fnlce@die!J4-d;1w)C91K8H^lO#m;S0B=iso-dUjEgnw)Q}GOQ`hx)<9RYC^oz- zeAdEojY}wVDPl$@b8)73nFR{C_jrg`XDe9^38@EN57wZ`nc&z|vRn#4m^XiFwAd0b zhS^rxtr0pXRzY2isGWebX!@PJI?xRa=ho>e*g$a!&1m6SJ#b zr(H5*Ey}+MG=yyZn^&Lm!vyAIyY<{dA@9~0Ejiofq6y3odi1?(%`Q;sA^qB3F7Ar& zSgz)Z+k~zaEXQs)S|0&6%IDgxHIZ^fC28x0k{2ZQnuYs0m2zZ%?hQU846u>;gjc7i zuy)71(mE;%Q%Q`+E4m-GH2$b_o3!hjZjDn~l8&xe`c3hA}H zAM<1QA!Lc-r1x=uy5qfldBS}oT-bxN_%U;6ro~o>$K4txp7Ui6wnFq?-s*+AE63em zg^O=_F~$BG(xT<8lTm&U&78@P6>EpBK`1|(1}+rV>)}j|%h!!jv5_W;ryMFh!&`rI zs*|8|8tJSlM2>L1p_p&h;FI!aa@J1>OE9MN8VE~_o*gwc!{8)6TW6AtltE!u+pSTA zj-*-V5D+>~+EIWB77gw2H%SC#PSms8O_H86uRi17)CMI<&BK0>7)VaN=nPH3Q~2z2 z!7l_RmZ5D~LbVp!%aAE<#G8rlxy&dm`)hIk)j@bUQkJlR(ijvv@opf{dUlpQ16oUw z>v!ig&}+y@d&2^_n8`Y8UGq?DMUjHr4}%1qR?YVY6O3wRyK^WCIcQoZ&QU^WX;GxD zkYZx)=Hk_*Zrv<=(AU*Y0rnc*V5AJ;;L;GXdX3->of@)HFZRbnDdLh2z+kN2qU5@! zDPc!xQAwB>y$_nybay4+dv!d1e*l?r30HMuq2g(_5LpizQX4MT#Y4m%RZZdS#Sl@Y z8vO+ml6Quig|m~8W3@;3%?>=@+}J48`}|q2G`i_UpQrU_mxb|6N@_?ist}*`bli#) zpZ5{gP8>NH+@8fcH-CXU>+f=gPe6`xJ5I1JkYIN%uawJY@6B#NT-TBDqE?+XDNJz$ ztc#dS+8eTzz(qx4&+JJfOzECyv6a#_d@qGn9pgSj3=jUK0 zEdg^%#$|@reh5p~IbIIrC7tIR9aFTL2X`7W+8Dku0j6)q$EXo=Bc>hy=kax-O$DX! z4+YB!&;T4zg5dxFo;JP-R|EpA9KRV2yT@SJs{{ys1!M1> xZ*;`1wDH=|F#zz|5}&rkl$V)>KLSCo3k%al>LQLjDpp`0B*Z5gPc1Zm`!5_5&29hy literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/union-tests-expected.png b/tests/regression/throwntogethertest/union-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..8ca55883aaab17e98692af767352109c4bef64b3 GIT binary patch literal 5815 zcmeHLYdlnI+rQ^v7>yjuVGC~v)pizbGGj|3r;c_#n4yp)N)5uyAV~*@QsWRarA8=} zoSK-Ch=gi{h+&uHtk5uH&Adzd`S5&wzrMfc)0$ap-S@Sw>;C_*>wjITULMYJvKq1g z06CZ4JNE&AgnuG|%<3gp!|@RS>jPYNIvj{bOb_C(==L_uj%e)7%**>NvM?#Y`NqjN z2{$Yaw|0g0nKU-Fv{?UU=OEIb>K)$ipJX$lmzTO}eVTDnJ)U zkAy-6DXfs1q7UznOw1a@|D2l(l_J<#I1{8U=mFq>1vCH@QUO5g|M%a2Bk_Mg!9iQ3 zj+99d6>8^;AB!M*Ie+`@OuN|Vr4L5H`?O%19=wvJCj&C-76ax-2TKL7B%ktXw4ZfN zpBa@g(F5F@C#6Weg?bBx9Cu)B00@^6eaCYlM-xELI|%-b%?P>4W;01i$f2|oc1WND zK+bXcmK6AUyO@klWs82cJM&|fh51Jj@D6PseOFKt0*+RRB_MmQu)SxyMDOe|;X@R7 zku>)9myby)X3jRk zPWF$!K7DH^(78b8Qy(u2m(ru!rDe7qnmEjR9rHD}bioN)!GrI_dbSF*xv=G#6xr->3TX_|`M$7}r$i zTrLI>=u*rQKZ?Xju-uJ%9wTae=jgY|Z-`~KP*^G*lVy+kz|*f-0>#BC@wE-;_i}cl z%~R!gh-aqfzpW*ydV1w5?3?tU4FsI7}i1Ub3ek+f_ySaOGhmk2U$ zSLb^JM`b``qJhqXe#Lu#e{DEoqUpGArU5!LD9i~Kn8#W z8G=?n*$;tp1^doK&foa$!4=p!yx}<2H zR~wW}v$Uy8_eCqP)7necM9xx-Adk31GPT*WHkQGO5#8}EbFEiI$V}gRhhLF#<+`YU z+mP0{u86~Pryl$Rap@b3E9&w9{$l)_GW4Lf8{22QUs_FSW1&(Xg!=~sL%ea(_-DSg z^*Q*|lhesd@~kzEg5}Z4sCCK{X*i8&&-P_gDVd=^N=3Ya1{xI8tze<9f%I>^w}?~Bzq z+?U04-GpHgLF>Lbr7u0uHb#-DtqX5gh<_`E3TgD z67~5GeqjqKcO2d{WbN{i>uX^HCSv5Sb^Zdz`8JoEI>SyUy96=%ed5LrNB2t6>FzWg zr&Xw*9!jSyu2fE(zrus!e|ip2C>c4t8K=g{P&=c}ox%wjCvtB#X&%b&j{YA0s&Bhc zPdR5$rFTq2ze40$@P=V#Q;x=!7ED=JP5W>?dxDw$L}Vc&2>VdtqOXv&rDTJHmQZP> z0Kul-X>;X1w%&;GHjhNd1Tw?M#D8MGf1OVGN}kc&$5u%(z2C3+m5C6(`Dkjtf=5c$TkX{B2SdAAH*3m^O`;rVXL+u`XC+ z&D^ZK{LeG;6bGY9(YP}03t3X^%+^EY-7z8sVsdVNl&tg;#Xr+G>nG8NUujd8zp}eb z;xft$sN@#U9I=lKmvnIhRHQ|dL=GevlEd2WEdYsfYMyEwU&fHNPz?+<(?^7P$*GY;En zZ^&a*;gD}Xbq&LL=doP;sS&1FKd1FHnJt% zfxglEQMZPV(5t)$I+g2K)GNfmRmfG%M@tU`PLn;zws;}x>usv)AoZI-baF0dTK=5D@Cr{4}t4JA;Xr97xFJD zMLhjjD^HCi%E6hkWbuVC_)*wc57pHdn;SH$X~Wcltk25*WzxxqeupdE-B?d{W!yRI zKKUDkY6Gz7coN$gW*c={xwL3W-xu}AtH9xR-|ztrvTuoZW8M6Pc|oHuHS1Ap{Cy~eLl5$1h;sQG8w|7 zzKrWjL$W{%}ZTxy#%bxO7w9r3VBD<*u zeEQ@)*^K5CX&{#X#8hyO~kAPLGJa8%TPl?0D@LlhamK{t@OFY!q>1)-*c za<65h7#6M;hCFvND zH{#6Mqg+W)&i2H|L&?*2R~9ATQ-~Pq8=ogpZZFzGg^wgIC!9?SVD${EyLe$&WjGgB zHk6UiaOKLk4#%rVeufUj+~!!ZsagoNdc@$^-xe9|3(kGeaW{!Rnk0KaNQXXX4Y3&Y zXwo0e$6#2LyHW2v;%l&S#K zD@1Ou77U!gnS;A9Zk+M4%kanBxmyTr8(^!6Q)hYM(B}$q2wV~o&oHp)XEg7(628k8 zXJNU4;WOkR%hH`1Z{t0-qgZ$i9>X4C=$fl;3J1*+?h{r;Vni}qsf{)$-@32{xYPz< z+$0~WrdMP+-NLb5047GH5szVnkh#;&eIRE@5#+USbHqq2LB$vKtSbrT$M~4hs+4&{ zgLln5EHRfM?4%##&Zsg&IvL?Sq7vfLWH&BbJnY5+5xK0xa4{SWj&{SiQ6pHK13A*a z>Sx(X&AU$a%`PbZZ6K)zjt!4k-LF^?LJ-t-(Z-9Oh!HYC!fGe*mMYUb;qO#P8<%$i z%$>iK#(U3fF%1n~hY`ju!GITG&sY>l`}oiAq`V zOC*o$EOo&0{v|vdL2~Q*$&_KqwvN||2&~dSor@u2_;Gcr7+s9&JSHW>iy%26g3h z4(0s;+eAkp=Ao)b;(Rt`-zMPv&76tIrl55mtU5F-!_r0=TB0<#JU4s{oCbJj<+#a5 zsF%HL6K^cucRRgdJ~M({j)(4@<4OGh`Mb>oYMjJ~*mi`iF; z1TWmO=!hs+GZ_0|&~L?FJQMHracRWvU37$8_J#2jUkm352y<5ZYMZdhuS$3KpS`rj z7iQU%?CU_!5rqj9I)Eqdt(dwfx)~IqhuCe7(ui2klQ)0^p|Z;N3b82*?vG1DVeqKg zx54=#>p>{N8B;(Kjwm5BTrig0g{HS?9c7I<1D?DiyzJ!afdo{FO_}|Q$5`;Xz0F63 z{t?KGVALP}<|LuN9x~&RG3vfGyzoI#n$A&}qUnDgrmFpGTE?{%etq4`g(gchQ|A8a z)>#ER>{cdJUYni8-s-(gHk!&&lbi1j|y{}0a_!)3<^4MASCn@oN0TNCcXaE2J literal 0 HcmV?d00001 From a1c698f422fceeadcf50e2ad456d5e43a01c8feb Mon Sep 17 00:00:00 2001 From: don bright Date: Mon, 10 Oct 2011 18:01:51 -0500 Subject: [PATCH 29/93] enable build of cgalpngtest under windowsXP --- cgal.pri | 3 ++- openscad.pro | 5 +++++ src/dxfdata.h | 8 ++++++++ src/func.cc | 1 + src/linalg.h | 4 ++++ src/linearextrude.cc | 1 + tests/CMakeLists.txt | 24 ++++++++++++++++++++---- tests/OffscreenView.cc | 2 +- tests/OffscreenView.h | 8 +++++++- tests/cgalpngtest.cc | 18 +++++++++++++----- tests/cgaltest.cc | 2 ++ tests/csgtestcore.cc | 2 +- tests/imageutils.cc | 4 ++-- 13 files changed, 67 insertions(+), 15 deletions(-) diff --git a/cgal.pri b/cgal.pri index 49e44de9..36afa486 100644 --- a/cgal.pri +++ b/cgal.pri @@ -17,6 +17,7 @@ cgal { LIBS += $$CGAL_DIR/auxiliary/gmp/lib/libmpfr-4.lib -lCGAL-vc90-mt-s } else { LIBS += -lgmp -lmpfr -lCGAL + QMAKE_CXXFLAGS += -frounding-math # visual C++ doesn't have this } - QMAKE_CXXFLAGS += -frounding-math + } diff --git a/openscad.pro b/openscad.pro index 3a418006..35f65ab3 100644 --- a/openscad.pro +++ b/openscad.pro @@ -42,6 +42,11 @@ win32:DEFINES += _USE_MATH_DEFINES NOMINMAX _CRT_SECURE_NO_WARNINGS YY_NO_UNISTD #disable warning about too long decorated names win32:QMAKE_CXXFLAGS += -wd4503 +#disable warning about casting int to bool +win32:QMAKE_CXXFLAGS += -wd4800 + +#disable warning about CGAL's unreferenced formal parameters +win32:QMAKE_CXXFLAGS += -wd4100 TEMPLATE = app RESOURCES = openscad.qrc diff --git a/src/dxfdata.h b/src/dxfdata.h index d8dc3dd1..04bcdd0f 100644 --- a/src/dxfdata.h +++ b/src/dxfdata.h @@ -1,10 +1,14 @@ #ifndef DXFDATA_H_ #define DXFDATA_H_ +// workaround Eigen SIMD alignment problems #ifndef __APPLE__ #define EIGEN_DONT_VECTORIZE 1 #define EIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT 1 #endif +#ifdef _MSC_VER +#define EIGEN_DONT_ALIGN +#endif #include #include @@ -35,7 +39,11 @@ public: } }; +#ifdef _MSC_VER + std::vector points; +#else std::vector > points; +#endif std::vector paths; std::vector dims; diff --git a/src/func.cc b/src/func.cc index b011a27f..75e90dfb 100644 --- a/src/func.cc +++ b/src/func.cc @@ -34,6 +34,7 @@ #include #include "stl-utils.h" #include +#include "openscad.h" //M_PI AbstractFunction::~AbstractFunction() { diff --git a/src/linalg.h b/src/linalg.h index 02fa17f7..b1e14094 100644 --- a/src/linalg.h +++ b/src/linalg.h @@ -1,10 +1,14 @@ #ifndef LINALG_H_ #define LINALG_H_ +// workaround Eigen SIMD alignment problems #ifndef __APPLE__ #define EIGEN_DONT_VECTORIZE 1 #define EIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT 1 #endif +#ifdef _MSC_VER +#define EIGEN_DONT_ALIGN +#endif #include #include diff --git a/src/linearextrude.cc b/src/linearextrude.cc index 5c3b6847..9c3557b9 100644 --- a/src/linearextrude.cc +++ b/src/linearextrude.cc @@ -32,6 +32,7 @@ #include "builtin.h" #include "PolySetEvaluator.h" #include "openscad.h" // get_fragments_from_r() +#include "mathc99.h" #include #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9e6640ed..d102f6bc 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,6 +8,16 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() +# Disable some warnings in Windows +if(WIN32) + # too long decorated names + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4503") + # int cast to bool in CGAL + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4800") + # unreferenced parameters in CGAL + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4100") +endif() + # # Build test apps # @@ -59,7 +69,7 @@ include_directories(${OPENCSG_INCLUDE_DIR}) # GLEW -if (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") +if () set(GLEW_DIR "$ENV{MACOSX_DEPLOY_DIR}") endif() find_package(GLEW REQUIRED) @@ -69,7 +79,10 @@ include_directories(${GLEW_INCLUDE_PATH}) find_package(BISON) find_package(FLEX) # The COMPILE_FLAGS and forced C++ compiler is just to be compatible with qmake -FLEX_TARGET(OpenSCADlexer ../src/lexer.l ${CMAKE_CURRENT_BINARY_DIR}/lexer.cpp COMPILE_FLAGS "-Plexer") +if (WIN32) + set(FLEX_UNISTD_FLAG "-DYY_NO_UNISTD_H") +endif() +FLEX_TARGET(OpenSCADlexer ../src/lexer.l ${CMAKE_CURRENT_BINARY_DIR}/lexer.cpp COMPILE_FLAGS "-Plexer ${FLEX_UNISTD_FLAG}") BISON_TARGET(OpenSCADparser ../src/parser.y ${CMAKE_CURRENT_BINARY_DIR}/parser_yacc.c COMPILE_FLAGS "-p parser") ADD_FLEX_BISON_DEPENDENCY(OpenSCADlexer OpenSCADparser) set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/parser_yacc.c PROPERTIES LANGUAGE "CXX") @@ -81,6 +94,7 @@ include_directories(../src) add_definitions(-DOPENSCAD_VERSION=test -DOPENSCAD_YEAR=2011 -DOPENSCAD_MONTH=10) set(COMMON_SOURCES + ../src/mathc99.cc ../src/handle_dep.cc ../src/qhash.cc ../src/export.cc @@ -121,10 +135,12 @@ set(COMMON_SOURCES # # Offscreen OpenGL context source code # -if (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") +if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") set(OFFSCREEN_CTX_SOURCE "OffscreenContext.mm") -else() +elseif(UNIX) set(OFFSCREEN_CTX_SOURCE "OffscreenContext.cc") +elseif(WIN32) + set(OFFSCREEN_CTX_SOURCE "OffscreenContextWGL.cc") endif() # diff --git a/tests/OffscreenView.cc b/tests/OffscreenView.cc index d188d0de..e34fbcea 100644 --- a/tests/OffscreenView.cc +++ b/tests/OffscreenView.cc @@ -186,7 +186,7 @@ void OffscreenView::paintGL() glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - glClearColor(1.0, 1.0, 0.92, 1.0); + glClearColor(1.0f, 1.0f, 0.92f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); diff --git a/tests/OffscreenView.h b/tests/OffscreenView.h index dd52a810..c57f07ea 100644 --- a/tests/OffscreenView.h +++ b/tests/OffscreenView.h @@ -1,15 +1,21 @@ #ifndef OFFSCREENVIEW_H_ #define OFFSCREENVIEW_H_ -#ifndef __APPLE__ // Eigen SIMD alignment +// workaround Eigen SIMD alignment problems +#ifndef __APPLE__ #define EIGEN_DONT_VECTORIZE 1 #define EIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT 1 #endif +#ifdef _MSC_VER +#define EIGEN_DONT_ALIGN +#endif #include "OffscreenContext.h" #include #include +#ifndef _MSC_VER #include +#endif class OffscreenView { diff --git a/tests/cgalpngtest.cc b/tests/cgalpngtest.cc index 447df456..fee44e84 100644 --- a/tests/cgalpngtest.cc +++ b/tests/cgalpngtest.cc @@ -23,6 +23,10 @@ * */ +#ifdef _MSC_VER +#define EIGEN_DONT_ALIGN +#endif + #include "myqhash.h" #include "openscad.h" #include "node.h" @@ -47,7 +51,9 @@ #include #include #include +#ifndef _MSC_VER #include +#endif #include #include #include @@ -182,7 +188,7 @@ int main(int argc, char **argv) try { csgInfo.glview = new OffscreenView(512,512); } catch (int error) { - fprintf(stderr,"Can't create OpenGL OffscreenView. exiting.\n"); + fprintf(stderr,"Can't create OpenGL OffscreenView. Code: %i. Exiting.\n", error); exit(1); } @@ -211,19 +217,21 @@ int main(int argc, char **argv) CGALRenderer cgalRenderer(N); - BoundingBox bbox; +/* Eigen::AlignedBox bbox; if (cgalRenderer.polyhedron) { CGAL::Bbox_3 cgalbbox = cgalRenderer.polyhedron->bbox(); bbox = BoundingBox(Vector3d(cgalbbox.xmin(), cgalbbox.ymin(), cgalbbox.zmin()), - Vector3d(cgalbbox.xmax(), cgalbbox.ymax(), cgalbbox.zmax())); + Vector3d(cgalbbox.xmax(), cgalbbox.ymax(), cgalbbox.zmax())); } else if (cgalRenderer.polyset) { bbox = cgalRenderer.polyset->getBoundingBox(); } + Vector3d center = (bbox.min() + bbox.max()) / 2; double radius = (bbox.max() - bbox.min()).norm() / 2; - - +*/ + Vector3d center(0,0,0); + double radius = 5.0; Vector3d cameradir(1, 1, -0.5); Vector3d camerapos = center - radius*2*cameradir; csgInfo.glview->setCamera(camerapos, center); diff --git a/tests/cgaltest.cc b/tests/cgaltest.cc index 8dfb63cb..2c83cc89 100644 --- a/tests/cgaltest.cc +++ b/tests/cgaltest.cc @@ -42,7 +42,9 @@ #include #include #include +#ifndef _MSC_VER #include +#endif #include #include #include diff --git a/tests/csgtestcore.cc b/tests/csgtestcore.cc index 2e28bf60..11ff6957 100644 --- a/tests/csgtestcore.cc +++ b/tests/csgtestcore.cc @@ -221,7 +221,7 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type) try { csgInfo.glview = new OffscreenView(512,512); } catch (int error) { - fprintf(stderr,"Can't create OpenGL OffscreenView. exiting.\n"); + fprintf(stderr,"Can't create OpenGL OffscreenView. Code: %i. Exiting.\n", error); exit(1); } BoundingBox bbox = csgInfo.root_chain->getBoundingBox(); diff --git a/tests/imageutils.cc b/tests/imageutils.cc index 7358674e..e15ba2b0 100644 --- a/tests/imageutils.cc +++ b/tests/imageutils.cc @@ -1,11 +1,11 @@ #include "imageutils.h" -#include +#include void flip_image(const unsigned char *src, unsigned char *dst, size_t pixelsize, size_t width, size_t height) { size_t rowBytes = pixelsize * width; for (size_t i = 0 ; i < height ; i++) { - bcopy(src + i * rowBytes, dst + (height - i - 1) * rowBytes, rowBytes); + memmove(dst + (height - i - 1) * rowBytes, src + i * rowBytes, rowBytes); } } From 31592577d3ce36c3651b7158c871e13d59fa0f1c Mon Sep 17 00:00:00 2001 From: don bright Date: Mon, 10 Oct 2011 21:46:27 -0500 Subject: [PATCH 30/93] enable building of opencsgtest, throwntogethertest, on WinXP --- openscad.pro | 4 ++++ tests/CMakeLists.txt | 25 +++++++++++++++++++++++++ tests/FindGLEW.cmake | 8 +++++++- tests/csgtermtest.cc | 2 ++ tests/csgtexttest.cc | 2 ++ tests/dumptest.cc | 2 ++ 6 files changed, 42 insertions(+), 1 deletion(-) diff --git a/openscad.pro b/openscad.pro index 35f65ab3..89fa738a 100644 --- a/openscad.pro +++ b/openscad.pro @@ -8,6 +8,10 @@ } } +win32 { + QMAKE_LFLAGS += -VERBOSE +} + win32 { isEmpty(VERSION) VERSION = $$system(date /t) } else { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d102f6bc..0a2a7642 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,6 +8,24 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() +set(WIN32_STATIC_BUILD "True") + +if(WIN32_STATIC_BUILD) + if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") + set(EMSG "\nTo build Win32 STATIC OpenSCAD you must run cmake .. -DCMAKE_BUILD_TYPE=Release") + set(EMSG "${EMSG} \nand replace /MD with /MT in CMakeCache.txt") + set(EMSG "${EMSG} \ni.e. sed -i s/\\/MD/\\/MT/ CMakeCache.txt") + message(FATAL_ERROR ${EMSG}) + endif() +endif() + +# Win32 linker debugging +# If you uncomment this, use nmake -F Makefile > log.txt +# (the link log can be several megabytes long) +if(WIN32) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -VERBOSE") +endif() + # Disable some warnings in Windows if(WIN32) # too long decorated names @@ -16,6 +34,8 @@ if(WIN32) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4800") # unreferenced parameters in CGAL set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4100") + # fopen_s advertisement + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_CRT_SECURE_NO_DEPRECATE") endif() # @@ -72,9 +92,14 @@ include_directories(${OPENCSG_INCLUDE_DIR}) if () set(GLEW_DIR "$ENV{MACOSX_DEPLOY_DIR}") endif() + find_package(GLEW REQUIRED) include_directories(${GLEW_INCLUDE_PATH}) +if(WIN32_STATIC_BUILD) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DGLEW_STATIC") +endif() + # Flex/Bison find_package(BISON) find_package(FLEX) diff --git a/tests/FindGLEW.cmake b/tests/FindGLEW.cmake index c2dc6a25..ffbdcc6e 100644 --- a/tests/FindGLEW.cmake +++ b/tests/FindGLEW.cmake @@ -16,7 +16,13 @@ IF (WIN32) ${PROJECT_SOURCE_DIR}/src/nvgl/glew/include DOC "The directory where GL/glew.h resides") FIND_LIBRARY( GLEW_LIBRARY - NAMES glew GLEW glew32 glew32s + + # Static linked Release (non-Debug) version + NAMES glew32s.lib + + # Dynamic linked Release (non-Debug) version + # NAMES glew32.lib + PATHS $ENV{PROGRAMFILES}/GLEW/lib ${PROJECT_SOURCE_DIR}/src/nvgl/glew/bin diff --git a/tests/csgtermtest.cc b/tests/csgtermtest.cc index 7688fc76..80b9b9c0 100644 --- a/tests/csgtermtest.cc +++ b/tests/csgtermtest.cc @@ -41,7 +41,9 @@ #include #include #include +#ifndef _MSC_VER #include +#endif #include #include #include diff --git a/tests/csgtexttest.cc b/tests/csgtexttest.cc index 3c4451d6..6badcfb0 100644 --- a/tests/csgtexttest.cc +++ b/tests/csgtexttest.cc @@ -39,7 +39,9 @@ #include #include #include +#ifndef _MSC_VER #include +#endif #include #include #include diff --git a/tests/dumptest.cc b/tests/dumptest.cc index 65424b3b..e9536893 100644 --- a/tests/dumptest.cc +++ b/tests/dumptest.cc @@ -39,7 +39,9 @@ #include #include #include +#ifndef _MSC_VER #include +#endif #include #include #include From 55f4a6150f4f8bc6fe5c3c17d3d38c73efefe9f2 Mon Sep 17 00:00:00 2001 From: don bright Date: Mon, 10 Oct 2011 21:51:46 -0500 Subject: [PATCH 31/93] enable test_cmdline to work in Windows --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0a2a7642..b9a53555 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -257,7 +257,7 @@ function(add_cmdline_test TESTCMD TESTSUFFIX) foreach (SCADFILE ${ARGN}) get_filename_component(TESTNAME ${SCADFILE} NAME_WE) string(REPLACE " " "_" TESTNAME ${TESTNAME}) # Test names cannot include spaces - add_test("${TESTCMD_NAME}_${TESTNAME}" ${tests_SOURCE_DIR}/test_cmdline_tool.py -s ${TESTSUFFIX} ${CMAKE_BINARY_DIR}/${TESTCMD} "${SCADFILE}") + add_test("${TESTCMD_NAME}_${TESTNAME}" python ${tests_SOURCE_DIR}/test_cmdline_tool.py -s ${TESTSUFFIX} ${CMAKE_BINARY_DIR}/${TESTCMD} "${SCADFILE}") endforeach() endfunction() From a9e4220796f2c82638027f1eef4ff393f1118c99 Mon Sep 17 00:00:00 2001 From: notroot Date: Tue, 11 Oct 2011 21:07:30 -0400 Subject: [PATCH 32/93] fix win compile switch --- tests/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b9a53555..dec1fa66 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,7 +8,9 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() -set(WIN32_STATIC_BUILD "True") +if (WIN32) + set(WIN32_STATIC_BUILD "True") +endif() if(WIN32_STATIC_BUILD) if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") From 17ccbace5ff035c156bc49042eab7af2c584b6aa Mon Sep 17 00:00:00 2001 From: don bright Date: Sat, 15 Oct 2011 15:48:41 -0500 Subject: [PATCH 33/93] improve documentation. move EIGEN_DONT_ALIGN to makefile. win32 build fixes. --- doc/testing.txt | 4 ++ openscad.pro | 7 ++- src/dxfdata.h | 15 +---- src/linalg.h | 9 --- tests/CMakeLists.txt | 42 ++++++++++--- tests/FindGLEW.cmake | 15 ++--- tests/OffscreenContext.cc | 43 +++++++------ tests/OffscreenContextWGL.cc | 117 +++++++++++++++++++++++++++++++++++ tests/OffscreenView.h | 9 --- tests/cgalpngtest.cc | 28 ++++----- 10 files changed, 206 insertions(+), 83 deletions(-) create mode 100644 tests/OffscreenContextWGL.cc diff --git a/doc/testing.txt b/doc/testing.txt index e6645fce..e265b963 100644 --- a/doc/testing.txt +++ b/doc/testing.txt @@ -10,6 +10,10 @@ cmake .. make make test +Building on Windows: +Follow http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Building_on_Windows +and build openscad, to verify your setup. Then, from the same command prompt, +run the above commands, but use 'nmake -f Makefile' instead of make Adding a new regression test: ------------------------------ diff --git a/openscad.pro b/openscad.pro index 89fa738a..bacb1228 100644 --- a/openscad.pro +++ b/openscad.pro @@ -9,11 +9,12 @@ } win32 { - QMAKE_LFLAGS += -VERBOSE +# for debugging link problems (use nmake -f Makefile.Release > log.txt) +# QMAKE_LFLAGS += -VERBOSE } win32 { - isEmpty(VERSION) VERSION = $$system(date /t) + isEmpty(VERSION) VERSION = "2011.10.15" } else { isEmpty(VERSION) VERSION = $$system(date "+%Y.%m.%d") } @@ -52,6 +53,8 @@ win32:QMAKE_CXXFLAGS += -wd4800 #disable warning about CGAL's unreferenced formal parameters win32:QMAKE_CXXFLAGS += -wd4100 +win32:QMAKE_CXXFLAGS += -DEIGEN_DONT_ALIGN + TEMPLATE = app RESOURCES = openscad.qrc diff --git a/src/dxfdata.h b/src/dxfdata.h index 04bcdd0f..d138e7a8 100644 --- a/src/dxfdata.h +++ b/src/dxfdata.h @@ -1,15 +1,6 @@ #ifndef DXFDATA_H_ #define DXFDATA_H_ -// workaround Eigen SIMD alignment problems -#ifndef __APPLE__ -#define EIGEN_DONT_VECTORIZE 1 -#define EIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT 1 -#endif -#ifdef _MSC_VER -#define EIGEN_DONT_ALIGN -#endif - #include #include @@ -39,10 +30,10 @@ public: } }; -#ifdef _MSC_VER - std::vector points; -#else +#ifdef __APPLE__ std::vector > points; +#else + std::vector points; #endif std::vector paths; std::vector dims; diff --git a/src/linalg.h b/src/linalg.h index b1e14094..06991cf5 100644 --- a/src/linalg.h +++ b/src/linalg.h @@ -1,15 +1,6 @@ #ifndef LINALG_H_ #define LINALG_H_ -// workaround Eigen SIMD alignment problems -#ifndef __APPLE__ -#define EIGEN_DONT_VECTORIZE 1 -#define EIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT 1 -#endif -#ifdef _MSC_VER -#define EIGEN_DONT_ALIGN -#endif - #include #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b9a53555..060e7726 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,25 +8,32 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() -set(WIN32_STATIC_BUILD "True") + +# +# Windows +# + +if(WIN32) + set(WIN32_STATIC_BUILD "True") +endif() if(WIN32_STATIC_BUILD) if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") - set(EMSG "\nTo build Win32 STATIC OpenSCAD you must run cmake .. -DCMAKE_BUILD_TYPE=Release") - set(EMSG "${EMSG} \nand replace /MD with /MT in CMakeCache.txt") + 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 ..") message(FATAL_ERROR ${EMSG}) endif() endif() -# Win32 linker debugging -# If you uncomment this, use nmake -F Makefile > log.txt -# (the link log can be several megabytes long) +# Turn off Eigen SIMD optimization if(WIN32) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -VERBOSE") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEIGEN_DONT_ALIGN") endif() -# Disable some warnings in Windows +# Disable warnings if(WIN32) # too long decorated names set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4503") @@ -38,6 +45,18 @@ if(WIN32) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_CRT_SECURE_NO_DEPRECATE") endif() +# Debugging - if you uncomment, use nmake -f Makefile > log.txt (the log is big) +if(WIN32) + # Linker debugging + #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -VERBOSE") + + # Compiler debugging + # you have to pass -DCMAKE_VERBOSE_MAKEFILE=ON to cmake when you run it. +endif() + +# Compilation debugging + + # # Build test apps # @@ -89,7 +108,7 @@ include_directories(${OPENCSG_INCLUDE_DIR}) # GLEW -if () +if (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") set(GLEW_DIR "$ENV{MACOSX_DEPLOY_DIR}") endif() @@ -161,10 +180,13 @@ set(COMMON_SOURCES # Offscreen OpenGL context source code # if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + message(STATUS "Offscreen OpenGL Context - using Apple CGL") set(OFFSCREEN_CTX_SOURCE "OffscreenContext.mm") elseif(UNIX) + message(STATUS "Offscreen OpenGL Context - using Unix GLX") set(OFFSCREEN_CTX_SOURCE "OffscreenContext.cc") elseif(WIN32) + message(STATUS "Offscreen OpenGL Context - using Microsoft WGL") set(OFFSCREEN_CTX_SOURCE "OffscreenContextWGL.cc") endif() @@ -212,7 +234,7 @@ target_link_libraries(cgaltest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${QT_ # # cgalpngtest # -add_executable(cgalpngtest cgalpngtest.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc fbo.cc +add_executable(cgalpngtest cgalpngtest.cc OffscreenView.cc bboxhelp.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc fbo.cc ../src/CGALRenderer.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CSGTermEvaluator.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc diff --git a/tests/FindGLEW.cmake b/tests/FindGLEW.cmake index ffbdcc6e..f49d5463 100644 --- a/tests/FindGLEW.cmake +++ b/tests/FindGLEW.cmake @@ -10,19 +10,20 @@ # a few lines of this file are based on the LGPL code found at # http://openlibraries.org/browser/trunk/FindGLEW.cmake?rev=1383 + IF (WIN32) + IF (WIN32_STATIC_BUILD) # passed from caller + SET(GLEW_LIB_SEARCH_NAME glew32s.lib) # static, non-debug (Release) + ELSE () + SET(GLEW_LIB_SEARCH_NAME glew32.lib) # other. untested with OpenSCAD + ENDIF() + FIND_PATH( GLEW_INCLUDE_PATH GL/glew.h $ENV{PROGRAMFILES}/GLEW/include ${PROJECT_SOURCE_DIR}/src/nvgl/glew/include DOC "The directory where GL/glew.h resides") FIND_LIBRARY( GLEW_LIBRARY - - # Static linked Release (non-Debug) version - NAMES glew32s.lib - - # Dynamic linked Release (non-Debug) version - # NAMES glew32.lib - + NAMES ${GLEW_LIB_SEARCH_NAME} PATHS $ENV{PROGRAMFILES}/GLEW/lib ${PROJECT_SOURCE_DIR}/src/nvgl/glew/bin diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc index 01ab9c4f..f5eabe03 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContext.cc @@ -1,9 +1,11 @@ -/* +/* Create an OpenGL context without creating an OpenGL Window. for Linux. -based on +See Also + glxgears.c by Brian Paul from mesa-demos (mesa3d.org) + http://cgit.freedesktop.org/mesa/demos/tree/src/xdemos?id=mesa-demos-8.0.1 http://www.opengl.org/sdk/docs/man/xhtml/glXIntro.xml http://www.mesa3d.org/brianp/sig97/offscrn.htm http://glprogramming.com/blue/ch07.html @@ -59,20 +61,13 @@ bool create_glx_dummy_window(OffscreenContext &ctx) { /* create a dummy X window without showing it. (without 'mapping' it) - save information to the ctx + and save information to the ctx. - based on http://www.opengl.org/sdk/docs/man/xhtml/glXIntro.xml - which was originally Copyright © 1991-2006 Silicon Graphics, Inc. - licensed under the SGI Free Software B License. - See http://oss.sgi.com/projects/FreeB/. + This purposely does not use glxCreateWindow, to avoid crashes, + "failed to create drawable" errors, and Mesa "WARNING: Application calling + GLX 1.3 function when GLX 1.3 is not supported! This is an application bug!" - also based on glxgears.c by Brian Paul from mesa-demos (mesa3d.org) - - purposely does not use glxCreateWindow, to avoid crashes, - "failed to create drawable" errors, and Mesa "This is an application bug!" - warnings about GLX 1.3. - - this function will alter ctx.openGLContext and ctx.xwindow if successfull + This function will alter ctx.openGLContext and ctx.xwindow if successfull */ int attributes[] = { @@ -86,8 +81,8 @@ bool create_glx_dummy_window(OffscreenContext &ctx) Display *dpy = ctx.xdisplay; - int numReturned = 0; - GLXFBConfig *fbconfigs = glXChooseFBConfig( dpy, DefaultScreen(dpy), attributes, &numReturned ); + int num_returned = 0; + GLXFBConfig *fbconfigs = glXChooseFBConfig( dpy, DefaultScreen(dpy), attributes, &num_returned ); if ( fbconfigs == NULL ) { cerr << "glXChooseFBConfig failed\n"; return false; @@ -100,9 +95,9 @@ bool create_glx_dummy_window(OffscreenContext &ctx) return false; } + // can't depend on xWin==NULL at failure. use a custom Xlib error handler instead. original_xlib_handler = XSetErrorHandler( XCreateWindow_error ); Window xWin = XCreateSimpleWindow( dpy, DefaultRootWindow(dpy), 0,0,10,10, 0,0,0 ); - // can't depend on xWin==NULL at failure. catch Xlib Errors instead. XSync( dpy, false ); if ( XCreateWindow_failed ) { XFree( visinfo ); @@ -110,7 +105,8 @@ bool create_glx_dummy_window(OffscreenContext &ctx) return false; } XSetErrorHandler( original_xlib_handler ); - // do not call XMapWindow - keep the window hidden + + // Most programs would call XMapWindow here. But we don't, to keep the window hidden GLXContext context = glXCreateNewContext( dpy, fbconfigs[0], GLX_RGBA_TYPE, NULL, True ); if ( context == NULL ) { @@ -153,13 +149,15 @@ Bool create_glx_dummy_context(OffscreenContext &ctx) return False; } + // glxQueryVersion is not always reliable. Use it, but then + // also check to see if GLX 1.3 functions exist + glXQueryVersion(ctx.xdisplay, &major, &minor); if ( major==1 && minor<=2 && glXGetVisualFromFBConfig==NULL ) { cerr << "Error: GLX version 1.3 functions missing. " << "Your GLX version: " << major << "." << minor << endl; } else { - // if glXGetVisualFromFBConfig exists, pretend we have >=1.3 result = create_glx_dummy_window(ctx); } @@ -198,7 +196,12 @@ OffscreenContext *create_offscreen_context(int w, int h) return NULL; } - glewInit(); //must come after Context creation and before FBO calls. + // glewInit must come after Context creation and before FBO calls. + glewInit(); + if (GLEW_OK != err) { + fprintf(stderr, "Unable to init GLEW: %s\n", glewGetErrorString(err)); + exit(1); + } glewCheck(); ctx->fbo = fbo_new(); diff --git a/tests/OffscreenContextWGL.cc b/tests/OffscreenContextWGL.cc new file mode 100644 index 00000000..7f3703df --- /dev/null +++ b/tests/OffscreenContextWGL.cc @@ -0,0 +1,117 @@ +/* + +Create an OpenGL context without creating an OpenGL Window. for Windows. + +*/ + +#include "OffscreenContext.h" +#include "printutils.h" +#include "imageutils.h" +#include "fbo.h" +#include +#include + +using namespace std; + +struct OffscreenContext +{ +// GLXContext openGLContext; + int width; + int height; + fbo_t *fbo; +}; + +void offscreen_context_init(OffscreenContext &ctx, int width, int height) +{ + ctx.width = width; + ctx.height = height; + ctx.fbo = NULL; +} + + +void glewCheck() { +#ifdef DEBUG + cerr << "GLEW version " << glewGetString(GLEW_VERSION) << "\n"; + cerr << (const char *)glGetString(GL_RENDERER) << "(" << (const char *)glGetString(GL_VENDOR) << ")\n" + << "OpenGL version " << (const char *)glGetString(GL_VERSION) << "\n"; + cerr << "Extensions: " << (const char *)glGetString(GL_EXTENSIONS) << "\n"; + + if (GLEW_ARB_framebuffer_object) { + cerr << "ARB_FBO supported\n"; + } + if (GLEW_EXT_framebuffer_object) { + cerr << "EXT_FBO supported\n"; + } + if (GLEW_EXT_packed_depth_stencil) { + cerr << "EXT_packed_depth_stencil\n"; + } +#endif +} + +OffscreenContext *create_offscreen_context(int w, int h) +{ + OffscreenContext *ctx = new OffscreenContext; + offscreen_context_init( *ctx, w, h ); + + // before an FBO can be setup, a GLX context must be created + // this call alters ctx->xDisplay and ctx->openGLContext + // and ctx->xwindow if successfull + cerr << "WGL not implemented\n"; +/* + if (!create_glx_dummy_context( *ctx )) { + return NULL; + } + + glewInit(); //must come after Context creation and before FBO calls. + glewCheck(); + + ctx->fbo = fbo_new(); + if (!fbo_init(ctx->fbo, w, h)) { + return NULL; + } + +*/ + ctx = NULL; + return ctx; +} + +bool teardown_offscreen_context(OffscreenContext *ctx) +{ + if (ctx) { + fbo_unbind(ctx->fbo); + fbo_delete(ctx->fbo); + return true; + } + return false; +} + +/*! + Capture framebuffer from OpenGL and write it to the given filename as PNG. +*/ +bool save_framebuffer(OffscreenContext *ctx, const char *filename) +{ + if (!ctx || !filename) return false; + int samplesPerPixel = 4; // R, G, B and A + vector pixels(ctx->width * ctx->height * samplesPerPixel); + glReadPixels(0, 0, ctx->width, ctx->height, GL_RGBA, GL_UNSIGNED_BYTE, &pixels[0]); + + // Flip it vertically - images read from OpenGL buffers are upside-down + 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."; + return 1; + } + flip_image(&pixels[0], flippedBuffer, samplesPerPixel, ctx->width, ctx->height); + + bool writeok = write_png(filename, flippedBuffer, ctx->width, ctx->height); + + free(flippedBuffer); + + return writeok; +} + +void bind_offscreen_context(OffscreenContext *ctx) +{ + if (ctx) fbo_bind(ctx->fbo); +} diff --git a/tests/OffscreenView.h b/tests/OffscreenView.h index c57f07ea..e3c8579c 100644 --- a/tests/OffscreenView.h +++ b/tests/OffscreenView.h @@ -1,15 +1,6 @@ #ifndef OFFSCREENVIEW_H_ #define OFFSCREENVIEW_H_ -// workaround Eigen SIMD alignment problems -#ifndef __APPLE__ -#define EIGEN_DONT_VECTORIZE 1 -#define EIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT 1 -#endif -#ifdef _MSC_VER -#define EIGEN_DONT_ALIGN -#endif - #include "OffscreenContext.h" #include #include diff --git a/tests/cgalpngtest.cc b/tests/cgalpngtest.cc index fee44e84..d43e810b 100644 --- a/tests/cgalpngtest.cc +++ b/tests/cgalpngtest.cc @@ -23,10 +23,6 @@ * */ -#ifdef _MSC_VER -#define EIGEN_DONT_ALIGN -#endif - #include "myqhash.h" #include "openscad.h" #include "node.h" @@ -88,6 +84,11 @@ struct CsgInfo OffscreenView *glview; }; + +extern Vector3d getBoundingCenter(BoundingBox bbox); +extern double getBoundingRadius(BoundingBox bbox); + + int main(int argc, char **argv) { if (argc != 2) { @@ -217,21 +218,19 @@ int main(int argc, char **argv) CGALRenderer cgalRenderer(N); -/* Eigen::AlignedBox bbox; + BoundingBox bbox; if (cgalRenderer.polyhedron) { 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) { + Vector3d min = Vector3d(cgalbbox.xmin(), cgalbbox.ymin(), cgalbbox.zmin()); + Vector3d max = Vector3d(cgalbbox.xmax(), cgalbbox.ymax(), cgalbbox.zmax()); + bbox = BoundingBox(min, max); + } else if (cgalRenderer.polyset) { bbox = cgalRenderer.polyset->getBoundingBox(); } - Vector3d center = (bbox.min() + bbox.max()) / 2; - double radius = (bbox.max() - bbox.min()).norm() / 2; -*/ - Vector3d center(0,0,0); - double radius = 5.0; + Vector3d center = getBoundingCenter(bbox); + double radius = getBoundingRadius(bbox); + Vector3d cameradir(1, 1, -0.5); Vector3d camerapos = center - radius*2*cameradir; csgInfo.glview->setCamera(camerapos, center); @@ -246,3 +245,4 @@ int main(int argc, char **argv) return 0; } + From 684b9eceb7b9cc3fdf734e34307b4d3987f1b462 Mon Sep 17 00:00:00 2001 From: don bright Date: Sat, 15 Oct 2011 16:44:52 -0500 Subject: [PATCH 34/93] fix bizarre win32 syntax error with eigen alignedbox --- tests/bboxhelp.cc | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/bboxhelp.cc diff --git a/tests/bboxhelp.cc b/tests/bboxhelp.cc new file mode 100644 index 00000000..b70ddcf4 --- /dev/null +++ b/tests/bboxhelp.cc @@ -0,0 +1,21 @@ +/* + Work around bugs in MSVC compiler with Eigen AlignmentBox + bbox.min and bbox.max will fail with Syntax Errors if placed inside + of cgalpngtest.cc +*/ + +#include "linalg.h" + +Vector3d getBoundingCenter(BoundingBox bbox) +{ + Vector3d center = (bbox.min() + bbox.max()) / 2; + return center; // Vector3d(0,0,0); +} + +double getBoundingRadius(BoundingBox bbox) +{ + double radius = (bbox.max() - bbox.min()).norm() / 2; + return radius; // 0; +} + + From 786dc3de411074a4b90fc51397c10539c45272f7 Mon Sep 17 00:00:00 2001 From: don bright Date: Sat, 15 Oct 2011 17:26:57 -0500 Subject: [PATCH 35/93] wgl window functions --- tests/OffscreenContextWGL.cc | 69 +++++++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/tests/OffscreenContextWGL.cc b/tests/OffscreenContextWGL.cc index 7f3703df..863da31b 100644 --- a/tests/OffscreenContextWGL.cc +++ b/tests/OffscreenContextWGL.cc @@ -4,6 +4,8 @@ Create an OpenGL context without creating an OpenGL Window. for Windows. */ +#include + #include "OffscreenContext.h" #include "printutils.h" #include "imageutils.h" @@ -15,7 +17,9 @@ using namespace std; struct OffscreenContext { -// GLXContext openGLContext; + HWND window; + HDC dev_context; + HGLRC openGLContext; int width; int height; fbo_t *fbo; @@ -23,6 +27,9 @@ struct OffscreenContext void offscreen_context_init(OffscreenContext &ctx, int width, int height) { + ctx.window = (HWND)NULL; + ctx.dev_context = (HDC)NULL; + ctx.openGLContext = (HGLRC)NULL; ctx.width = width; ctx.height = height; ctx.fbo = NULL; @@ -48,17 +55,60 @@ void glewCheck() { #endif } +LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) +{ + return DefWindowProc( hwnd, message, wparam, lparam ); +} + +bool create_wgl_dummy_context(OffscreenContext &ctx) +{ + // create window + + HINSTANCE inst = GetModuleHandle(0); + WNDCLASS wc; + ZeroMemory( &wc, sizeof( wc ) ); + wc.style = CS_OWNDC; + wc.lpfnWndProc = WndProc; + wc.hInstance = inst; + wc.lpszClassName = "OpenSCAD"; + RegisterClass( &wc ); + + HWND window = CreateWindow( "OpenSCAD", "OpenSCAD", + WS_CAPTION | WS_POPUPWINDOW | WS_VISIBLE, + 0, 0, 256, 256, NULL, NULL, inst, NULL ); + + + // create WGL context, make current + + PIXELFORMATDESCRIPTOR pixformat; + int chosenformat; + HDC dev_context = GetDC( window ); + ZeroMemory( &pixformat, sizeof( pixformat ) ); + pixformat.nSize = sizeof( pixformat ); + pixformat.nVersion = 1; + pixformat.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL; + pixformat.iPixelType = PFD_TYPE_RGBA; + pixformat.cColorBits = 24; + pixformat.cDepthBits = 16; + pixformat.iLayerType = PFD_MAIN_PLANE; + chosenformat = ChoosePixelFormat( dev_context, &pixformat ); + SetPixelFormat( dev_context, chosenformat, &pixformat ); + HGLRC gl_render_context = wglCreateContext( dev_context ); + wglMakeCurrent( dev_context, gl_render_context ); + + return true; +} + + OffscreenContext *create_offscreen_context(int w, int h) { OffscreenContext *ctx = new OffscreenContext; offscreen_context_init( *ctx, w, h ); - // before an FBO can be setup, a GLX context must be created - // this call alters ctx->xDisplay and ctx->openGLContext - // and ctx->xwindow if successfull - cerr << "WGL not implemented\n"; -/* - if (!create_glx_dummy_context( *ctx )) { + // before an FBO can be setup, a WGL context must be created + // this call alters ctx->window and ctx->openGLContext + // and ctx->dev_context if successfull + if (!create_wgl_dummy_context( *ctx )) { return NULL; } @@ -70,7 +120,6 @@ OffscreenContext *create_offscreen_context(int w, int h) return NULL; } -*/ ctx = NULL; return ctx; } @@ -78,6 +127,10 @@ OffscreenContext *create_offscreen_context(int w, int h) bool teardown_offscreen_context(OffscreenContext *ctx) { if (ctx) { + wglMakeCurrent( NULL, NULL ); + wglDeleteContext( ctx->openGLContext ); + ReleaseDC( ctx->window, ctx->dev_context ); + fbo_unbind(ctx->fbo); fbo_delete(ctx->fbo); return true; From e79ee827185e19daac8f4d2385cce44098374d64 Mon Sep 17 00:00:00 2001 From: don bright Date: Sat, 15 Oct 2011 22:59:54 -0500 Subject: [PATCH 36/93] fix text tests for windows + enable framebuffer tests --- tests/CMakeLists.txt | 3 +- tests/OffscreenContextWGL.cc | 124 ++++++++++++++++++++++++----------- tests/cgalpngtest.cc | 7 +- tests/csgtermtest.cc | 13 ++-- tests/csgtestcore.cc | 7 +- tests/csgtexttest.cc | 11 +++- tests/dumptest.cc | 11 +++- tests/imageutils-lodepng.cc | 3 +- tests/test_cmdline_tool.py | 15 +++-- 9 files changed, 133 insertions(+), 61 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 84e0562e..593fa1b0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -273,12 +273,13 @@ target_link_libraries(throwntogethertest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRAR # This functions adds cmd-line tests given files. # Files are sent as the parameters following TESTSUFFIX # +find_package(PythonInterp) function(add_cmdline_test TESTCMD TESTSUFFIX) get_filename_component(TESTCMD_NAME ${TESTCMD} NAME_WE) foreach (SCADFILE ${ARGN}) get_filename_component(TESTNAME ${SCADFILE} NAME_WE) string(REPLACE " " "_" TESTNAME ${TESTNAME}) # Test names cannot include spaces - add_test("${TESTCMD_NAME}_${TESTNAME}" python ${tests_SOURCE_DIR}/test_cmdline_tool.py -s ${TESTSUFFIX} ${CMAKE_BINARY_DIR}/${TESTCMD} "${SCADFILE}") + add_test("${TESTCMD_NAME}_${TESTNAME}" ${PYTHON_EXECUTABLE} ${tests_SOURCE_DIR}/test_cmdline_tool.py -s ${TESTSUFFIX} ${CMAKE_BINARY_DIR}/${TESTCMD} "${SCADFILE}") endforeach() endfunction() diff --git a/tests/OffscreenContextWGL.cc b/tests/OffscreenContextWGL.cc index 863da31b..17c49c6e 100644 --- a/tests/OffscreenContextWGL.cc +++ b/tests/OffscreenContextWGL.cc @@ -2,6 +2,13 @@ Create an OpenGL context without creating an OpenGL Window. for Windows. +For more info: + + http://www.nullterminator.net/opengl32.html by Blaine Hodge + http://msdn.microsoft.com/en-us/library/ee418815(v=vs.85).aspx + http://www.cprogramming.com/tutorial/wgl_wiggle_functions.html by RoD + ( which includes robot.cc by Steven Billington ) + http://blogs.msdn.com/b/oldnewthing/archive/2006/12/04/1205831.aspx by Tom */ #include @@ -35,13 +42,12 @@ void offscreen_context_init(OffscreenContext &ctx, int width, int height) ctx.fbo = NULL; } - void glewCheck() { #ifdef DEBUG cerr << "GLEW version " << glewGetString(GLEW_VERSION) << "\n"; cerr << (const char *)glGetString(GL_RENDERER) << "(" << (const char *)glGetString(GL_VENDOR) << ")\n" << "OpenGL version " << (const char *)glGetString(GL_VERSION) << "\n"; - cerr << "Extensions: " << (const char *)glGetString(GL_EXTENSIONS) << "\n"; + //cerr << "Extensions: " << (const char *)glGetString(GL_EXTENSIONS) << "\n"; if (GLEW_ARB_framebuffer_object) { cerr << "ARB_FBO supported\n"; @@ -57,46 +63,81 @@ void glewCheck() { LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { - return DefWindowProc( hwnd, message, wparam, lparam ); + return DefWindowProc( hwnd, message, wparam, lparam ); } bool create_wgl_dummy_context(OffscreenContext &ctx) { - // create window + // this call alters ctx->window and ctx->openGLContext + // and ctx->dev_context if successfull + // create window - HINSTANCE inst = GetModuleHandle(0); - WNDCLASS wc; - ZeroMemory( &wc, sizeof( wc ) ); - wc.style = CS_OWNDC; - wc.lpfnWndProc = WndProc; - wc.hInstance = inst; - wc.lpszClassName = "OpenSCAD"; - RegisterClass( &wc ); + HINSTANCE inst = GetModuleHandle(0); + WNDCLASS wc; + ZeroMemory( &wc, sizeof( wc ) ); + wc.style = CS_OWNDC; + wc.lpfnWndProc = WndProc; + wc.hInstance = inst; + wc.lpszClassName = "OpenSCAD"; + RegisterClass( &wc ); - HWND window = CreateWindow( "OpenSCAD", "OpenSCAD", - WS_CAPTION | WS_POPUPWINDOW | WS_VISIBLE, - 0, 0, 256, 256, NULL, NULL, inst, NULL ); - - - // create WGL context, make current + HWND window = CreateWindow( "OpenSCAD", "OpenSCAD", + WS_CAPTION | WS_POPUPWINDOW, //| WS_VISIBLE, + 0, 0, ctx.width, ctx.height, NULL, NULL, inst, NULL ); - PIXELFORMATDESCRIPTOR pixformat; - int chosenformat; - HDC dev_context = GetDC( window ); - ZeroMemory( &pixformat, sizeof( pixformat ) ); - pixformat.nSize = sizeof( pixformat ); - pixformat.nVersion = 1; - pixformat.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL; - pixformat.iPixelType = PFD_TYPE_RGBA; - pixformat.cColorBits = 24; - pixformat.cDepthBits = 16; - pixformat.iLayerType = PFD_MAIN_PLANE; - chosenformat = ChoosePixelFormat( dev_context, &pixformat ); - SetPixelFormat( dev_context, chosenformat, &pixformat ); - HGLRC gl_render_context = wglCreateContext( dev_context ); - wglMakeCurrent( dev_context, gl_render_context ); + if ( window==NULL ) { + cerr << "MS GDI - CreateWindow failed\n"; + return false; + } - return true; + // create WGL context, make current + + PIXELFORMATDESCRIPTOR pixformat; + int chosenformat; + HDC dev_context = GetDC( window ); + if ( dev_context == NULL ) { + cerr << "MS GDI - GetDC failed\n"; + return false; + } + + ZeroMemory( &pixformat, sizeof( pixformat ) ); + pixformat.nSize = sizeof( pixformat ); + pixformat.nVersion = 1; + pixformat.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL; + pixformat.iPixelType = PFD_TYPE_RGBA; + pixformat.cColorBits = 24; + pixformat.cDepthBits = 16; + pixformat.iLayerType = PFD_MAIN_PLANE; + chosenformat = ChoosePixelFormat( dev_context, &pixformat ); + if (chosenformat==0) { + cerr << "MS GDI - ChoosePixelFormat failed\n"; + return false; + } + + bool spfok = SetPixelFormat( dev_context, chosenformat, &pixformat ); + if (!spfok) { + cerr << "MS GDI - SetPixelFormat failed\n"; + return false; + } + + HGLRC gl_render_context = wglCreateContext( dev_context ); + if ( gl_render_context == NULL ) { + cerr << "MS WGL - wglCreateContext failed\n"; + ReleaseDC( ctx.window, ctx.dev_context ); + return false; + } + + bool mcok = wglMakeCurrent( dev_context, gl_render_context ); + if (!mcok) { + cerr << "MS GDI - wglMakeCurrent failed\n"; + return false; + } + + ctx.window = window; + ctx.dev_context = dev_context; + ctx.openGLContext = gl_render_context; + + return true; } @@ -105,22 +146,29 @@ OffscreenContext *create_offscreen_context(int w, int h) OffscreenContext *ctx = new OffscreenContext; offscreen_context_init( *ctx, w, h ); - // before an FBO can be setup, a WGL context must be created - // this call alters ctx->window and ctx->openGLContext + // Before an FBO can be setup, a WGL context must be created. + // This call alters ctx->window and ctx->openGLContext // and ctx->dev_context if successfull if (!create_wgl_dummy_context( *ctx )) { + cerr << "wgl dummy fail\n"; + fflush(stderr); return NULL; } - glewInit(); //must come after Context creation and before FBO calls. + GLenum err = glewInit(); // must come after Context creation and before FBO calls. + if (GLEW_OK != err) { + cerr << "Unable to init GLEW: " << glewGetErrorString(err) << "\n"; + fflush(stderr); + return NULL; + } glewCheck(); ctx->fbo = fbo_new(); if (!fbo_init(ctx->fbo, w, h)) { + cerr << "fbo didn't init\n"; return NULL; } - ctx = NULL; return ctx; } diff --git a/tests/cgalpngtest.cc b/tests/cgalpngtest.cc index d43e810b..c72bc30a 100644 --- a/tests/cgalpngtest.cc +++ b/tests/cgalpngtest.cc @@ -91,12 +91,13 @@ extern double getBoundingRadius(BoundingBox bbox); int main(int argc, char **argv) { - if (argc != 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); + if (argc != 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); exit(1); } const char *filename = argv[1]; + const char *outfile = argv[2]; initialize_builtin_functions(); initialize_builtin_modules(); @@ -238,7 +239,7 @@ int main(int argc, char **argv) csgInfo.glview->setRenderer(&cgalRenderer); csgInfo.glview->paintGL(); - csgInfo.glview->save("/dev/stdout"); + csgInfo.glview->save(outfile); destroy_builtin_functions(); destroy_builtin_modules(); diff --git a/tests/csgtermtest.cc b/tests/csgtermtest.cc index 80b9b9c0..643a64fc 100644 --- a/tests/csgtermtest.cc +++ b/tests/csgtermtest.cc @@ -47,6 +47,7 @@ #include #include #include +#include using std::cout; @@ -57,12 +58,13 @@ QString librarydir; int main(int argc, char **argv) { - if (argc != 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); + if (argc != 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); exit(1); } const char *filename = argv[1]; + const char *outfilename = argv[2]; int rc = 0; @@ -161,12 +163,15 @@ int main(int argc, char **argv) // if (evaluator.background) cout << "Background terms: " << evaluator.background->size() << "\n"; // if (evaluator.highlights) cout << "Highlights terms: " << evaluator.highlights->size() << "\n"; + std::ofstream outfile; + outfile.open(outfilename); if (root_term) { - cout << root_term->dump() << "\n"; + outfile << root_term->dump() << "\n"; } else { - cout << "No top-level CSG object\n"; + outfile << "No top-level CSG object\n"; } + outfile.close(); destroy_builtin_functions(); destroy_builtin_modules(); diff --git a/tests/csgtestcore.cc b/tests/csgtestcore.cc index 11ff6957..e87558d8 100644 --- a/tests/csgtestcore.cc +++ b/tests/csgtestcore.cc @@ -69,12 +69,13 @@ AbstractNode *find_root_tag(AbstractNode *n) int csgtestcore(int argc, char *argv[], test_type_e test_type) { - if (argc < 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); + if (argc != 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); exit(1); } std::string filename(argv[1]); + std::string outfile(argv[2]); initialize_builtin_functions(); initialize_builtin_modules(); @@ -244,7 +245,7 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type) csgInfo.glview->paintGL(); - csgInfo.glview->save("/dev/stdout"); + csgInfo.glview->save(outfile.c_str()); destroy_builtin_functions(); destroy_builtin_modules(); diff --git a/tests/csgtexttest.cc b/tests/csgtexttest.cc index 6badcfb0..21ebf6a3 100644 --- a/tests/csgtexttest.cc +++ b/tests/csgtexttest.cc @@ -45,6 +45,7 @@ #include #include #include +#include std::string commandline_commands; QString currentdir; @@ -60,12 +61,13 @@ void csgTree(CSGTextCache &cache, const AbstractNode &root) int main(int argc, char **argv) { - if (argc != 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); + if (argc != 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); exit(1); } const char *filename = argv[1]; + const char *outfilename = argv[2]; int rc = 0; @@ -151,7 +153,10 @@ int main(int argc, char **argv) csgTree(csgcache, *root_node); // std::cout << tree.getString(*root_node) << "\n"; - std::cout << csgcache[*root_node] << "\n"; + std::ofstream outfile; + outfile.open(outfilename); + outfile << csgcache[*root_node] << "\n"; + outfile.close(); destroy_builtin_functions(); destroy_builtin_modules(); diff --git a/tests/dumptest.cc b/tests/dumptest.cc index e9536893..37824462 100644 --- a/tests/dumptest.cc +++ b/tests/dumptest.cc @@ -45,6 +45,7 @@ #include #include #include +#include using std::string; @@ -55,12 +56,13 @@ QString librarydir; int main(int argc, char **argv) { - if (argc != 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); + if (argc != 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); exit(1); } const char *filename = argv[1]; + const char *outfilename = argv[2]; int rc = 0; @@ -148,7 +150,10 @@ int main(int argc, char **argv) string dumpstdstr_cached = tree.getString(*root_node); assert(dumpstdstr == dumpstdstr_cached); - std::cout << dumpstdstr << "\n"; + std::ofstream outfile; + outfile.open(outfilename); + outfile << dumpstdstr << "\n"; + outfile.close(); destroy_builtin_functions(); destroy_builtin_modules(); diff --git a/tests/imageutils-lodepng.cc b/tests/imageutils-lodepng.cc index 8636fa44..8460d9ef 100644 --- a/tests/imageutils-lodepng.cc +++ b/tests/imageutils-lodepng.cc @@ -10,7 +10,8 @@ bool write_png(const char *filename, unsigned char *pixels, int width, int heigh unsigned char *dataout = (unsigned char *)malloc(width*height*4); LodePNG_encode(&dataout, &dataout_size, pixels, width, height, LCT_RGBA, 8); //LodePNG_saveFile(dataout, dataout_size, "blah2.png"); - FILE *f = fopen(filename, "w"); + + FILE *f = fopen(filename, "wb"); if (!f) { free(dataout); return false; diff --git a/tests/test_cmdline_tool.py b/tests/test_cmdline_tool.py index 3ac41dd2..f4b9138b 100755 --- a/tests/test_cmdline_tool.py +++ b/tests/test_cmdline_tool.py @@ -57,6 +57,7 @@ def execute_and_redirect(cmd, params, outfile): return retval def get_normalized_text(filename): + print >> sys.stderr, "debug normalize" , filename text = open(filename).read() return text.strip("\r\n").replace("\r\n", "\n") + "\n" @@ -70,10 +71,10 @@ def compare_default(resultfilename): return True def append_html_output(expectedfilename, resultfilename): - # if html directory & file not there, create them - # copy expected filename and result filename to dir - # append html to show differences - # dump platform.platform() + # 1 if html directory & file not there, create them + # 2 copy expected filename image and result filename image to dir + # 3 append html to show differences + # 4 dump platform.platform() expectedimg = os.path.basename(expectedfilename) resultimg = os.path.basename(resultfilename) template = ''' @@ -133,6 +134,7 @@ def run_test(testname, cmd, args): outputdir = os.path.join(os.getcwd(), cmdname + "-output") actualfilename = os.path.join(outputdir, testname + "-actual." + options.suffix) + actualfilename = os.path.normpath(actualfilename) if options.generate: if not os.path.exists(expecteddir): os.makedirs(expecteddir) @@ -140,9 +142,12 @@ def run_test(testname, cmd, args): else: if not os.path.exists(outputdir): os.makedirs(outputdir) outputname = actualfilename + outputname = os.path.normpath( outputname ) + outfile = open(outputname, "wb") try: - proc = subprocess.Popen([cmd] + args, stdout=outfile, stderr=subprocess.PIPE) + print >> sys.stderr, "debug ", [cmd], args, [outputname] + proc = subprocess.Popen([cmd] + args + [outputname], stdout=subprocess.PIPE, stderr=subprocess.PIPE) errtext = proc.communicate()[1] if errtext != None and len(errtext) > 0: print >> sys.stderr, "Error output: " + errtext From 7735f5510cdc2d396092d04ed98a2dbaec347608 Mon Sep 17 00:00:00 2001 From: don bright Date: Sat, 15 Oct 2011 23:44:21 -0500 Subject: [PATCH 37/93] opencsgtest and throwntogethertest windows fix --- tests/OffscreenContextWGL.cc | 6 +++++- tests/opencsgtest.cc | 2 +- tests/test_cmdline_tool.py | 2 -- tests/throwntogethertest.cc | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/OffscreenContextWGL.cc b/tests/OffscreenContextWGL.cc index 17c49c6e..b4170d10 100644 --- a/tests/OffscreenContextWGL.cc +++ b/tests/OffscreenContextWGL.cc @@ -68,8 +68,12 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) bool create_wgl_dummy_context(OffscreenContext &ctx) { - // this call alters ctx->window and ctx->openGLContext + // this function alters ctx->window and ctx->openGLContext // and ctx->dev_context if successfull + + // stop Windows from producing dialog boxes about "this application has failed" + SetErrorMode(SEM_NOGPFAULTERRORBOX|SEM_NOOPENFILEERRORBOX|SEM_FAILCRITICALERRORS); + // create window HINSTANCE inst = GetModuleHandle(0); diff --git a/tests/opencsgtest.cc b/tests/opencsgtest.cc index 9ddf6622..06cfe862 100644 --- a/tests/opencsgtest.cc +++ b/tests/opencsgtest.cc @@ -1,5 +1,5 @@ #include "csgtestcore.h" int main(int argc, char* argv[]) { - return csgtestcore(argc+1, argv, TEST_OPENCSG); + return csgtestcore(argc, argv, TEST_OPENCSG); } diff --git a/tests/test_cmdline_tool.py b/tests/test_cmdline_tool.py index f4b9138b..e6a84cff 100755 --- a/tests/test_cmdline_tool.py +++ b/tests/test_cmdline_tool.py @@ -57,7 +57,6 @@ def execute_and_redirect(cmd, params, outfile): return retval def get_normalized_text(filename): - print >> sys.stderr, "debug normalize" , filename text = open(filename).read() return text.strip("\r\n").replace("\r\n", "\n") + "\n" @@ -146,7 +145,6 @@ def run_test(testname, cmd, args): outfile = open(outputname, "wb") try: - print >> sys.stderr, "debug ", [cmd], args, [outputname] proc = subprocess.Popen([cmd] + args + [outputname], stdout=subprocess.PIPE, stderr=subprocess.PIPE) errtext = proc.communicate()[1] if errtext != None and len(errtext) > 0: diff --git a/tests/throwntogethertest.cc b/tests/throwntogethertest.cc index ba0a6820..5a4f14f5 100644 --- a/tests/throwntogethertest.cc +++ b/tests/throwntogethertest.cc @@ -1,5 +1,5 @@ #include "csgtestcore.h" int main(int argc, char* argv[]) { - return csgtestcore(argc+1, argv, TEST_THROWNTOGETHER); + return csgtestcore(argc, argv, TEST_THROWNTOGETHER); } From 7332ec9fe260140ff74506fa2ba95e0a2bbf352e Mon Sep 17 00:00:00 2001 From: Don Bright Date: Sat, 15 Oct 2011 20:14:35 -0500 Subject: [PATCH 38/93] clean up linux changes to fit with win32 changes. --- doc/testing.txt | 18 ++++++++++++++---- openscad.pro | 5 ++++- src/csgterm.cc | 20 -------------------- src/csgterm.h | 1 - src/mainwin.cc | 12 ------------ tests/CMakeLists.txt | 13 ++++++------- tests/OffscreenContext.cc | 2 +- tests/OffscreenContextWGL.cc | 9 ++------- tests/cgalpngtest.cc | 5 ++--- tests/csgtestcore.h | 5 +++++ tests/lodepng.h | 6 ++++++ tests/yee_compare.h | 8 ++++---- 12 files changed, 44 insertions(+), 60 deletions(-) diff --git a/doc/testing.txt b/doc/testing.txt index e265b963..c5fb2439 100644 --- a/doc/testing.txt +++ b/doc/testing.txt @@ -10,10 +10,20 @@ cmake .. make make test -Building on Windows: -Follow http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Building_on_Windows -and build openscad, to verify your setup. Then, from the same command prompt, -run the above commands, but use 'nmake -f Makefile' instead of make +Windows: + +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 +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +sed -i s/\/MD/\/MT/ CMakeCache.txt +cmake .. +nmake -f Makefile +nmake -f Makefile test Adding a new regression test: ------------------------------ diff --git a/openscad.pro b/openscad.pro index bacb1228..49553ada 100644 --- a/openscad.pro +++ b/openscad.pro @@ -14,7 +14,8 @@ win32 { } win32 { - isEmpty(VERSION) VERSION = "2011.10.15" + isEmpty(VERSION) VERSION = $$system(date /t) + # isEmpty(VERSION) VERSION = "2011.10.15" # for XP, set version manually } else { isEmpty(VERSION) VERSION = $$system(date "+%Y.%m.%d") } @@ -53,7 +54,9 @@ win32:QMAKE_CXXFLAGS += -wd4800 #disable warning about CGAL's unreferenced formal parameters win32:QMAKE_CXXFLAGS += -wd4100 +# disable Eigen SIMD optimizations for non-Mac OSX win32:QMAKE_CXXFLAGS += -DEIGEN_DONT_ALIGN +unix:QMAKE_CXXFLAGS += -DEIGEN_DONT_ALIGN TEMPLATE = app RESOURCES = openscad.qrc diff --git a/src/csgterm.cc b/src/csgterm.cc index c0eb2c88..16ef75fc 100644 --- a/src/csgterm.cc +++ b/src/csgterm.cc @@ -227,26 +227,6 @@ std::string CSGChain::dump() return dump.str(); } - -std::string CSGChain::fulldump() -{ - std::stringstream dump; - dump << "\nsizes: \n"; - dump << " polysets: " << polysets.size(); - dump << " matrices: " << matrices.size(); - dump << " colors: " << colors.size(); - dump << " types: " << types.size(); - dump << " labels: " << labels.size(); - dump << "\ndata: \n"; - for (size_t i = 0; i < polysets.size(); i++) dump << (*polysets[i]).polygons.size() << "\n"; - for (size_t i = 0; i < matrices.size(); i++) dump << matrices[i].matrix() << "\n"; - for (size_t i = 0; i < colors.size(); i++) dump << *colors[i] << "\n"; - for (size_t i = 0; i < types.size(); i++) dump << types[i] << "\n"; - for (size_t i = 0; i < labels.size(); i++) dump << labels[i] << "\n"; - dump << "\n"; - return dump.str(); -} - BoundingBox CSGChain::getBoundingBox() const { BoundingBox bbox; diff --git a/src/csgterm.h b/src/csgterm.h index b09b2d25..1d9d9fd4 100644 --- a/src/csgterm.h +++ b/src/csgterm.h @@ -52,7 +52,6 @@ public: void add(const shared_ptr &polyset, const Transform3d &m, double *color, CSGTerm::type_e type, std::string label); void import(CSGTerm *term, CSGTerm::type_e type = CSGTerm::TYPE_UNION); std::string dump(); - std::string fulldump(); BoundingBox getBoundingBox() const; }; diff --git a/src/mainwin.cc b/src/mainwin.cc index fafe8edb..dfa6f95e 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -873,18 +873,6 @@ void MainWindow::compileCSG(bool procevents) this->thrownTogetherRenderer = new ThrownTogetherRenderer(this->root_chain, this->highlights_chain, this->background_chain); - - fprintf(stderr, "Dump root chain\n"); - cerr << this->root_chain->fulldump(); - cerr << this->highlights_chain; - cerr << this->background_chain; -/* fprintf(stderr, "dump highlights\n"); - this->highlights_chain->dump(); - fprintf(stderr, "dump background\n"); - this->background_chain->dump();*/ - fprintf(stderr, "end dump\n"); - - PRINT("CSG generation finished."); int s = t.elapsed() / 1000; PRINTF("Total rendering time: %d hours, %d minutes, %d seconds", s / (60*60), (s / 60) % 60, s % 60); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 593fa1b0..35818de4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,11 +27,6 @@ if(WIN32_STATIC_BUILD) endif() endif() -# Turn off Eigen SIMD optimization -if(WIN32) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEIGEN_DONT_ALIGN") -endif() - # Disable warnings if(WIN32) # too long decorated names @@ -53,8 +48,6 @@ if(WIN32) # you have to pass -DCMAKE_VERBOSE_MAKEFILE=ON to cmake when you run it. endif() -# Compilation debugging - # # Build test apps @@ -71,6 +64,12 @@ find_package(Qt4 COMPONENTS QtCore QtGui QtOpenGL REQUIRED) include(${QT_USE_FILE}) # Eigen2 + +# Turn off Eigen SIMD optimization +if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEIGEN_DONT_ALIGN") +endif() + if (NOT EIGEN2_INCLUDE_DIR) find_path(EIGEN2_INCLUDE_DIR Eigen/Core diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc index f5eabe03..c50097f3 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContext.cc @@ -197,7 +197,7 @@ OffscreenContext *create_offscreen_context(int w, int h) } // glewInit must come after Context creation and before FBO calls. - glewInit(); + GLenum err = glewInit(); if (GLEW_OK != err) { fprintf(stderr, "Unable to init GLEW: %s\n", glewGetErrorString(err)); exit(1); diff --git a/tests/OffscreenContextWGL.cc b/tests/OffscreenContextWGL.cc index b4170d10..f1203842 100644 --- a/tests/OffscreenContextWGL.cc +++ b/tests/OffscreenContextWGL.cc @@ -71,9 +71,6 @@ bool create_wgl_dummy_context(OffscreenContext &ctx) // this function alters ctx->window and ctx->openGLContext // and ctx->dev_context if successfull - // stop Windows from producing dialog boxes about "this application has failed" - SetErrorMode(SEM_NOGPFAULTERRORBOX|SEM_NOOPENFILEERRORBOX|SEM_FAILCRITICALERRORS); - // create window HINSTANCE inst = GetModuleHandle(0); @@ -84,7 +81,7 @@ bool create_wgl_dummy_context(OffscreenContext &ctx) wc.hInstance = inst; wc.lpszClassName = "OpenSCAD"; RegisterClass( &wc ); - + HWND window = CreateWindow( "OpenSCAD", "OpenSCAD", WS_CAPTION | WS_POPUPWINDOW, //| WS_VISIBLE, 0, 0, ctx.width, ctx.height, NULL, NULL, inst, NULL ); @@ -133,7 +130,7 @@ bool create_wgl_dummy_context(OffscreenContext &ctx) bool mcok = wglMakeCurrent( dev_context, gl_render_context ); if (!mcok) { - cerr << "MS GDI - wglMakeCurrent failed\n"; + cerr << "MS WGL - wglMakeCurrent failed\n"; return false; } @@ -154,7 +151,6 @@ OffscreenContext *create_offscreen_context(int w, int h) // This call alters ctx->window and ctx->openGLContext // and ctx->dev_context if successfull if (!create_wgl_dummy_context( *ctx )) { - cerr << "wgl dummy fail\n"; fflush(stderr); return NULL; } @@ -169,7 +165,6 @@ OffscreenContext *create_offscreen_context(int w, int h) ctx->fbo = fbo_new(); if (!fbo_init(ctx->fbo, w, h)) { - cerr << "fbo didn't init\n"; return NULL; } diff --git a/tests/cgalpngtest.cc b/tests/cgalpngtest.cc index c72bc30a..7f505a6e 100644 --- a/tests/cgalpngtest.cc +++ b/tests/cgalpngtest.cc @@ -222,9 +222,8 @@ int main(int argc, char **argv) BoundingBox bbox; if (cgalRenderer.polyhedron) { CGAL::Bbox_3 cgalbbox = cgalRenderer.polyhedron->bbox(); - Vector3d min = Vector3d(cgalbbox.xmin(), cgalbbox.ymin(), cgalbbox.zmin()); - Vector3d max = Vector3d(cgalbbox.xmax(), cgalbbox.ymax(), cgalbbox.zmax()); - bbox = BoundingBox(min, max); + bbox = BoundingBox(Vector3d(cgalbbox.xmin(), cgalbbox.ymin(), cgalbbox.zmin()), + Vector3d(cgalbbox.xmax(), cgalbbox.ymax(), cgalbbox.zmax())); } else if (cgalRenderer.polyset) { bbox = cgalRenderer.polyset->getBoundingBox(); } diff --git a/tests/csgtestcore.h b/tests/csgtestcore.h index b988b217..330c8942 100644 --- a/tests/csgtestcore.h +++ b/tests/csgtestcore.h @@ -1,3 +1,6 @@ +#ifndef CSGTESTCORE_H_ +#define CSGTESTCORE_H_ + enum test_type_e { TEST_THROWNTOGETHER, TEST_OPENCSG @@ -5,3 +8,5 @@ enum test_type_e { int csgtestcore(int argc, char *argv[], test_type_e test_type); +#endif + diff --git a/tests/lodepng.h b/tests/lodepng.h index 64ec2e31..a0a654f7 100644 --- a/tests/lodepng.h +++ b/tests/lodepng.h @@ -1,3 +1,6 @@ +#ifndef LODEPNG_H_ +#define LODEPNG_H_ + /* LodePNG version 20110908 @@ -1891,3 +1894,6 @@ Account: lode dot vandevenne. Copyright (c) 2005-2011 Lode Vandevenne */ + +#endif + diff --git a/tests/yee_compare.h b/tests/yee_compare.h index 17b2e6fa..041ae4c9 100644 --- a/tests/yee_compare.h +++ b/tests/yee_compare.h @@ -1,4 +1,8 @@ +#ifndef _yee_compare_h +#define _yee_compare_h + // source code modified for OpenSCAD, Sept 2011 +// original copyright notice follows: /* Metric RGBAImage.h @@ -18,9 +22,6 @@ You should have received a copy of the GNU General Public License along with thi if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#ifndef _yee_compare_h -#define _yee_compare_h - #include class RGBAImage; @@ -123,4 +124,3 @@ protected: }; #endif - From de53382a3d1a7f47684cdda7c27d976c2e234ce9 Mon Sep 17 00:00:00 2001 From: Don Bright Date: Sat, 15 Oct 2011 20:22:06 -0500 Subject: [PATCH 39/93] small cleanup --- tests/OffscreenContextWGL.cc | 2 -- tests/cgalpngtest.cc | 5 +++-- tests/csgtestcore.cc | 12 ++++++------ 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/tests/OffscreenContextWGL.cc b/tests/OffscreenContextWGL.cc index f1203842..844c0734 100644 --- a/tests/OffscreenContextWGL.cc +++ b/tests/OffscreenContextWGL.cc @@ -151,14 +151,12 @@ OffscreenContext *create_offscreen_context(int w, int h) // This call alters ctx->window and ctx->openGLContext // and ctx->dev_context if successfull if (!create_wgl_dummy_context( *ctx )) { - fflush(stderr); return NULL; } GLenum err = glewInit(); // must come after Context creation and before FBO calls. if (GLEW_OK != err) { cerr << "Unable to init GLEW: " << glewGetErrorString(err) << "\n"; - fflush(stderr); return NULL; } glewCheck(); diff --git a/tests/cgalpngtest.cc b/tests/cgalpngtest.cc index 7f505a6e..41e7eaf9 100644 --- a/tests/cgalpngtest.cc +++ b/tests/cgalpngtest.cc @@ -223,8 +223,9 @@ int main(int argc, char **argv) if (cgalRenderer.polyhedron) { 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) { + Vector3d(cgalbbox.xmax(), cgalbbox.ymax(), cgalbbox.zmax())); + } + else if (cgalRenderer.polyset) { bbox = cgalRenderer.polyset->getBoundingBox(); } diff --git a/tests/csgtestcore.cc b/tests/csgtestcore.cc index e87558d8..6c3a7d0f 100644 --- a/tests/csgtestcore.cc +++ b/tests/csgtestcore.cc @@ -219,12 +219,12 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type) QDir::setCurrent(original_path.absolutePath()); - try { - csgInfo.glview = new OffscreenView(512,512); - } catch (int error) { - fprintf(stderr,"Can't create OpenGL OffscreenView. Code: %i. Exiting.\n", error); - exit(1); - } + try { + csgInfo.glview = new OffscreenView(512,512); + } catch (int error) { + fprintf(stderr,"Can't create OpenGL OffscreenView. Code: %i. Exiting.\n", error); + exit(1); + } BoundingBox bbox = csgInfo.root_chain->getBoundingBox(); Vector3d center = (bbox.min() + bbox.max()) / 2; From adf7cd51fbd02309a33604223004efe6a3da9380 Mon Sep 17 00:00:00 2001 From: notroot Date: Sun, 16 Oct 2011 14:15:08 +0000 Subject: [PATCH 40/93] clarify error messages. --- tests/OffscreenContext.cc | 20 ++++++++++---------- tests/fbo.cc | 8 ++++---- tests/system-gl.cc | 26 +++++++++++++------------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc index 7a3637c1..e4045af4 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContext.cc @@ -46,10 +46,10 @@ void offscreen_context_init(OffscreenContext &ctx, int width, int height) static XErrorHandler original_xlib_handler = (XErrorHandler) NULL; static bool XCreateWindow_failed = false; -static int XCreateWindow_error(Display *dpy, XErrorEvent *event) +static int XCreateWindow_error(Display *dpy, XErrorEvent *event) { - cerr << "XCreateWindow failed: XID: " << event->resourceid - << " request: " << (int)event->request_code + cerr << "XCreateWindow failed: XID: " << event->resourceid + << " request: " << (int)event->request_code << " minor: " << (int)event->minor_code << "\n"; char description[1024]; XGetErrorText( dpy, event->error_code, description, 1023 ); @@ -58,7 +58,7 @@ static int XCreateWindow_error(Display *dpy, XErrorEvent *event) return 0; } -bool create_glx_dummy_window(OffscreenContext &ctx) +bool create_glx_dummy_window(OffscreenContext &ctx) { /* create a dummy X window without showing it. (without 'mapping' it) @@ -108,7 +108,7 @@ bool create_glx_dummy_window(OffscreenContext &ctx) XSetErrorHandler( original_xlib_handler ); // Most programs would call XMapWindow here. But we don't, to keep the window hidden - XMapWindow( dpy, xWin ); + // XMapWindow( dpy, xWin ); GLXContext context = glXCreateNewContext( dpy, fbconfigs[0], GLX_RGBA_TYPE, NULL, True ); if ( context == NULL ) { @@ -119,10 +119,10 @@ bool create_glx_dummy_window(OffscreenContext &ctx) return false; } - GLXWindow glxWin = glXCreateWindow( dpy, fbconfigs[0], xWin, NULL ); + //GLXWindow glxWin = glXCreateWindow( dpy, fbconfigs[0], xWin, NULL ); - //if (!glXMakeContextCurrent( dpy, xWin, xWin, context )) { - if (!glXMakeContextCurrent( dpy, glxWin, glxWin, context )) { + if (!glXMakeContextCurrent( dpy, xWin, xWin, context )) { + //if (!glXMakeContextCurrent( dpy, glxWin, glxWin, context )) { cerr << "glXMakeContextCurrent failed\n"; glXDestroyContext( dpy, context ); XDestroyWindow( dpy, xWin ); @@ -188,11 +188,11 @@ 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)) { - cerr << "Framebuffer init failed; dumping GLEW info" << endl; - glew_dump(); + cerr << "GL Framebuffer Object init failed; dumping GLEW info" << endl; return NULL; } diff --git a/tests/fbo.cc b/tests/fbo.cc index 42dacaa3..2978e59b 100644 --- a/tests/fbo.cc +++ b/tests/fbo.cc @@ -81,20 +81,20 @@ bool fbo_ext_init(fbo_t *fbo, size_t width, size_t height) // Attach render and depth buffers glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, fbo->renderbuf_id); - if (report_glerror("specifying color render buffer")) return false; + if (report_glerror("specifying color render buffer EXT")) return false; if (!check_fbo_status()) { - cerr << "Problem with OpenGL framebuffer after specifying color render buffer.\n"; + cerr << "Problem with OpenGL EXT framebuffer after specifying color render buffer.\n"; return false; } glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo->depthbuf_id); - if (report_glerror("specifying depth render buffer")) return false; + if (report_glerror("specifying depth render buffer EXT")) return false; if (!check_fbo_status()) { - cerr << "Problem with OpenGL framebuffer after specifying depth render buffer.\n"; + cerr << "Problem with OpenGL EXT framebuffer after specifying depth render buffer.\n"; return false; } diff --git a/tests/system-gl.cc b/tests/system-gl.cc index b7c7dd62..92dea5e3 100644 --- a/tests/system-gl.cc +++ b/tests/system-gl.cc @@ -14,16 +14,16 @@ void glew_dump() { << "Vendor: " << (const char *)glGetString(GL_VENDOR) << endl << "OpenGL version: " << (const char *)glGetString(GL_VERSION) << endl; - string extensions((const char *)glGetString(GL_EXTENSIONS)); - replace_all( extensions, " ", "\n " ); - cerr << "Extensions: " << endl << " " << extensions << endl - << "Framebuffer and Stencil support:" << endl - << " GLEW_ARB_framebuffer_object: " << (bool)GLEW_ARB_framebuffer_object << endl - << " GLEW_EXT_framebuffer_object: " << (bool)GLEW_EXT_framebuffer_object << endl - << " GLEW_EXT_packed_depth_stencil: " << (bool)GLEW_EXT_packed_depth_stencil << endl - << " glewIsSupported(\"GL_ARB_framebuffer_object\"): " << (bool)glewIsSupported("GL_ARB_framebuffer_object") << endl - << " glewIsSupported(\"GL_EXT_framebuffer_object\"): " << (bool)glewIsSupported("GL_EXT_framebuffer_object") << endl - << " glewIsSupported(\"GL_EXT_packed_depth_stencil\"): " << (bool)glewIsSupported("GL_EXT_packed_depth_stencil") << endl; + bool dumpall = false; + if (dumpall) { + string extensions((const char *)glGetString(GL_EXTENSIONS)); + replace_all( extensions, " ", "\n " ); + cerr << "Extensions: " << endl << " " << extensions << endl; + } + + cerr << " GLEW_ARB_framebuffer_object: " << ((GLEW_ARB_framebuffer_object==1) ? "yes" : "no" ) << endl + << " GLEW_EXT_framebuffer_object: " << ((GLEW_EXT_framebuffer_object==1) ? "yes" : "no") << endl + << " GLEW_EXT_packed_depth_stencil: " << ((GLEW_EXT_packed_depth_stencil==1) ? "yes" : "no") << endl; }; const char * gl_errors[] = { @@ -33,14 +33,14 @@ const char * gl_errors[] = { "GL_OUT_OF_MEMORY" // 0x0503 }; -bool report_glerror(const char * task) +bool report_glerror(const char * function) { GLenum tGLErr = glGetError(); if (tGLErr != GL_NO_ERROR) { if ( (tGLErr-0x500)<=3 && (tGLErr-0x500)>=0 ) - cerr << "OpenGL error " << gl_errors[tGLErr-0x500] << " while " << task << endl; + cerr << "OpenGL error " << gl_errors[tGLErr-0x500] << " after " << function << endl; else - cerr << "OpenGL error " << hex << tGLErr << " while " << task << endl; + cerr << "OpenGL error 0x" << hex << tGLErr << " after " << function << endl; return true; } return false; From 43839582e930f49a279bac9ff16d5f10fa449a8e Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 16 Oct 2011 17:50:33 +0200 Subject: [PATCH 41/93] Merged --- src/value.cc | 6 +- testdata/scad/features/echo-tests.scad | 12 ++ tests/CMakeLists.txt | 24 ++- tests/echotest.cc | 148 ++++++++++++++++++ tests/regression/echotest/echo-expected.txt | 1 + .../echotest/echo-tests-expected.txt | 8 + 6 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 testdata/scad/features/echo-tests.scad create mode 100644 tests/echotest.cc create mode 100644 tests/regression/echotest/echo-expected.txt create mode 100644 tests/regression/echotest/echo-tests-expected.txt diff --git a/src/value.cc b/src/value.cc index 685bf81a..6685594d 100644 --- a/src/value.cc +++ b/src/value.cc @@ -352,19 +352,19 @@ std::string Value::toString() const stream << ']'; break; case RANGE: - stream << "[ " + stream << '[' << this->range_begin << " : " << this->range_step << " : " << this->range_end - << " ]"; + << ']'; break; case NUMBER: stream << this->num; break; case BOOL: - stream << this->b; + stream << (this->b ? "true" : "false"); break; default: stream << "undef"; diff --git a/testdata/scad/features/echo-tests.scad b/testdata/scad/features/echo-tests.scad new file mode 100644 index 00000000..b07d444c --- /dev/null +++ b/testdata/scad/features/echo-tests.scad @@ -0,0 +1,12 @@ +echo(undef); +echo("string"); +s = "stringvar"; +echo(s); +echo(a = 1, b = 2.0, true, c = false); +v = [1, "vecstr", 2.34, false]; +echo(v); +r = [1:2:10]; +echo(r); + +echo(vec = [1,2,3]); +echo(range = [0:2]); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 35818de4..20cba6b4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,7 @@ cmake_minimum_required(VERSION 2.8) +# Explicitly use new include policy to avoid globally shadowing included modules +# http://www.cmake.org/cmake/help/cmake-2-8-docs.html#policy:CMP0017 +cmake_policy(SET CMP0017 NEW) project(tests) set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}") @@ -135,11 +138,10 @@ include_directories(../src) add_definitions(-DOPENSCAD_VERSION=test -DOPENSCAD_YEAR=2011 -DOPENSCAD_MONTH=10) -set(COMMON_SOURCES +set(CORE_SOURCES ../src/mathc99.cc ../src/handle_dep.cc ../src/qhash.cc - ../src/export.cc ../src/value.cc ../src/expr.cc ../src/func.cc @@ -165,6 +167,12 @@ set(COMMON_SOURCES ../src/rotateextrude.cc ../src/printutils.cc ../src/progress.cc + ${FLEX_OpenSCADlexer_OUTPUTS} + ${BISON_OpenSCADparser_OUTPUTS}) + +set(COMMON_SOURCES + ${CORE_SOURCES} + ../src/export.cc ../src/nodedumper.cc ../src/traverser.cc ../src/PolySetEvaluator.cc @@ -174,6 +182,12 @@ set(COMMON_SOURCES ${FLEX_OpenSCADlexer_OUTPUTS} ${BISON_OpenSCADparser_OUTPUTS}) +# +# echotest +# +add_executable(echotest echotest.cc ${CORE_SOURCES}) +target_link_libraries(echotest ${QT_LIBRARIES} ${OPENGL_LIBRARY}) + # # Offscreen OpenGL context source code # @@ -290,6 +304,12 @@ file(GLOB FEATURES_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/features/*.scad) file(GLOB BUGS_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/bugs/*.scad) file(GLOB SCAD_DXF_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/dxf/*.scad) +list(APPEND ECHO_FILES + ${CMAKE_SOURCE_DIR}/../testdata/scad/minimal/echo.scad + ${CMAKE_SOURCE_DIR}/../testdata/scad/features/echo-tests.scad) + +# Add echotest tests to CTest +add_cmdline_test(echotest txt ${ECHO_FILES}) # Add dumptest tests to CTest add_cmdline_test(dumptest txt ${MINIMAL_FILES}) # Add csgtexttest tests to CTest diff --git a/tests/echotest.cc b/tests/echotest.cc new file mode 100644 index 00000000..4811c7cf --- /dev/null +++ b/tests/echotest.cc @@ -0,0 +1,148 @@ +/* + * OpenSCAD (www.openscad.org) + * Copyright (C) 2009-2011 Clifford Wolf and + * Marius Kintel + * + * 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. + * + * As a special exception, you have permission to link this program + * with the CGAL library and distribute executables, as long as you + * follow the requirements of the GNU GPL in regard to all of the + * software in the executable aside from CGAL. + * + * 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 "openscad.h" +#include "handle_dep.h" +#include "node.h" +#include "module.h" +#include "context.h" +#include "value.h" +#include "builtin.h" +#include "printutils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using std::string; + +std::string commandline_commands; +QString currentdir; +QString examplesdir; +QString librarydir; + +static void stdout_handler(const QString &msg, void *userdata) { + std::cout << msg.toUtf8().data() << std::endl; +} + +int main(int argc, char **argv) +{ + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(1); + } + + const char *filename = argv[1]; + + int rc = 0; + + set_output_handler(&stdout_handler, NULL); + + initialize_builtin_functions(); + initialize_builtin_modules(); + + QApplication app(argc, argv, false); + QDir original_path = QDir::current(); + + currentdir = QDir::currentPath(); + + QDir libdir(QApplication::instance()->applicationDirPath()); +#ifdef Q_WS_MAC + libdir.cd("../Resources"); // Libraries can be bundled + if (!libdir.exists("libraries")) libdir.cd("../../.."); +#elif defined(Q_OS_UNIX) + if (libdir.cd("../share/openscad/libraries")) { + librarydir = libdir.path(); + } else + if (libdir.cd("../../share/openscad/libraries")) { + librarydir = libdir.path(); + } else + if (libdir.cd("../../libraries")) { + librarydir = libdir.path(); + } else +#endif + if (libdir.cd("libraries")) { + librarydir = libdir.path(); + } + + Context root_ctx; + root_ctx.functions_p = &builtin_functions; + root_ctx.modules_p = &builtin_modules; + root_ctx.set_variable("$fn", Value(0.0)); + root_ctx.set_variable("$fs", Value(1.0)); + root_ctx.set_variable("$fa", Value(12.0)); + root_ctx.set_variable("$t", Value(0.0)); + + Value zero3; + zero3.type = Value::VECTOR; + zero3.append(new Value(0.0)); + zero3.append(new Value(0.0)); + zero3.append(new Value(0.0)); + root_ctx.set_variable("$vpt", zero3); + root_ctx.set_variable("$vpr", zero3); + + + AbstractModule *root_module; + ModuleInstantiation root_inst; + AbstractNode *root_node; + + QFileInfo fileInfo(filename); + handle_dep(filename); + FILE *fp = fopen(filename, "rt"); + if (!fp) { + fprintf(stderr, "Can't open input file `%s'!\n", filename); + exit(1); + } else { + std::stringstream text; + char buffer[513]; + int ret; + while ((ret = fread(buffer, 1, 512, fp)) > 0) { + buffer[ret] = 0; + text << buffer; + } + fclose(fp); + text << commandline_commands; + root_module = parse(text.str().c_str(), fileInfo.absolutePath().toLocal8Bit(), false); + if (!root_module) { + exit(1); + } + } + + QDir::setCurrent(fileInfo.absolutePath()); + + AbstractNode::resetIndexCounter(); + root_node = root_module->evaluate(&root_ctx, &root_inst); + + destroy_builtin_functions(); + destroy_builtin_modules(); + + return rc; +} diff --git a/tests/regression/echotest/echo-expected.txt b/tests/regression/echotest/echo-expected.txt new file mode 100644 index 00000000..8fc094e3 --- /dev/null +++ b/tests/regression/echotest/echo-expected.txt @@ -0,0 +1 @@ +ECHO: diff --git a/tests/regression/echotest/echo-tests-expected.txt b/tests/regression/echotest/echo-tests-expected.txt new file mode 100644 index 00000000..cc548f67 --- /dev/null +++ b/tests/regression/echotest/echo-tests-expected.txt @@ -0,0 +1,8 @@ +ECHO: undef +ECHO: "string" +ECHO: "stringvar" +ECHO: a = 1, b = 2, true, c = false +ECHO: [1, "vecstr", 2.34, false] +ECHO: [1 : 2 : 10] +ECHO: vec = [1, 2, 3] +ECHO: range = [0 : 1 : 2] From df7626a022707509bc55300d28c3f0878d622c48 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 16 Oct 2011 17:50:55 +0200 Subject: [PATCH 42/93] Modified to write to explicit filename --- tests/echotest.cc | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/tests/echotest.cc b/tests/echotest.cc index 4811c7cf..c53e47fc 100644 --- a/tests/echotest.cc +++ b/tests/echotest.cc @@ -41,6 +41,7 @@ #include #include #include +#include using std::string; @@ -49,22 +50,29 @@ QString currentdir; QString examplesdir; QString librarydir; -static void stdout_handler(const QString &msg, void *userdata) { - std::cout << msg.toUtf8().data() << std::endl; +static void outfile_handler(const QString &msg, void *userdata) { + std::ostream *str = static_cast(userdata); + *str << msg.toUtf8().data() << std::endl; } int main(int argc, char **argv) { - if (argc != 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); + if (argc != 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); exit(1); } const char *filename = argv[1]; + const char *outfile = argv[2]; int rc = 0; - set_output_handler(&stdout_handler, NULL); + std::ofstream ofile(outfile); + if (!ofile.good()) { + std::cerr << "Unable to open output file\n"; + return 0; + } + set_output_handler(&outfile_handler, &ofile); initialize_builtin_functions(); initialize_builtin_modules(); @@ -144,5 +152,6 @@ int main(int argc, char **argv) destroy_builtin_functions(); destroy_builtin_modules(); + ofile.close(); return rc; } From 006329bff09d4ffb8b6302ee49a33ec2b483f908 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 16 Oct 2011 17:52:20 +0200 Subject: [PATCH 43/93] Testing with different color space - should hopefully create more similar output images --- tests/imageutils-macosx.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/imageutils-macosx.cc b/tests/imageutils-macosx.cc index 358bdcf9..404052fb 100644 --- a/tests/imageutils-macosx.cc +++ b/tests/imageutils-macosx.cc @@ -4,7 +4,8 @@ bool write_png(const char *filename, unsigned char *pixels, int width, int height) { size_t rowBytes = width * 4; - CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); +// CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGBitmapInfo bitmapInfo = kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big; // BGRA int bitsPerComponent = 8; CGContextRef contextRef = CGBitmapContextCreate(pixels, width, height, From 24634b3eccbe6bc934481b3da128c910c607a130 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 16 Oct 2011 18:06:36 +0200 Subject: [PATCH 44/93] merged --- tests/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 20cba6b4..80af81f0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -188,6 +188,12 @@ set(COMMON_SOURCES add_executable(echotest echotest.cc ${CORE_SOURCES}) target_link_libraries(echotest ${QT_LIBRARIES} ${OPENGL_LIBRARY}) +# +# echotest +# +add_executable(echotest echotest.cc ${CORE_SOURCES}) +target_link_libraries(echotest ${QT_LIBRARIES} ${OPENGL_LIBRARY}) + # # Offscreen OpenGL context source code # From 790c9a19258876b93781b70af14c71915a746904 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 16 Oct 2011 18:08:34 +0200 Subject: [PATCH 45/93] Increase pixel diff threshold --- tests/test_cmdline_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cmdline_tool.py b/tests/test_cmdline_tool.py index e6a84cff..b6afbecb 100755 --- a/tests/test_cmdline_tool.py +++ b/tests/test_cmdline_tool.py @@ -117,7 +117,7 @@ def compare_png(resultfilename): print >> sys.stderr, "Error: OpenSCAD did not generate an image" return False print >> sys.stderr, 'Yee image compare: ', expectedfilename, ' ', resultfilename - if execute_and_redirect("./yee_compare", [expectedfilename, resultfilename, "-downsample", "2"], sys.stderr) != 0: + if execute_and_redirect("./yee_compare", [expectedfilename, resultfilename, "-downsample", "2", "-threshold", "200"], sys.stderr) != 0: append_html_output(expectedfilename, resultfilename) return False return True From c0592dc3b833a91457e28a6d80bcc58c1bb7938d Mon Sep 17 00:00:00 2001 From: Don Bright Date: Sun, 16 Oct 2011 11:52:13 -0500 Subject: [PATCH 46/93] fix small bugs 1. error reporting 2. fbo teardown --- tests/OffscreenContext.cc | 4 ++-- tests/OffscreenContextWGL.cc | 5 +++-- tests/cgalpngtest.cc | 3 +-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc index c50097f3..06799627 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContext.cc @@ -110,7 +110,7 @@ bool create_glx_dummy_window(OffscreenContext &ctx) GLXContext context = glXCreateNewContext( dpy, fbconfigs[0], GLX_RGBA_TYPE, NULL, True ); if ( context == NULL ) { - cerr << "glXGetVisualFromFBConfig failed\n"; + cerr << "glXCreateNewContext failed\n"; XDestroyWindow( dpy, xWin ); XFree( visinfo ); XFree( fbconfigs ); @@ -150,7 +150,7 @@ Bool create_glx_dummy_context(OffscreenContext &ctx) } // glxQueryVersion is not always reliable. Use it, but then - // also check to see if GLX 1.3 functions exist + // also check to see if GLX 1.3 functions exist glXQueryVersion(ctx.xdisplay, &major, &minor); diff --git a/tests/OffscreenContextWGL.cc b/tests/OffscreenContextWGL.cc index 844c0734..7e976762 100644 --- a/tests/OffscreenContextWGL.cc +++ b/tests/OffscreenContextWGL.cc @@ -172,12 +172,13 @@ OffscreenContext *create_offscreen_context(int w, int h) bool teardown_offscreen_context(OffscreenContext *ctx) { if (ctx) { + fbo_unbind(ctx->fbo); + fbo_delete(ctx->fbo); + wglMakeCurrent( NULL, NULL ); wglDeleteContext( ctx->openGLContext ); ReleaseDC( ctx->window, ctx->dev_context ); - fbo_unbind(ctx->fbo); - fbo_delete(ctx->fbo); return true; } return false; diff --git a/tests/cgalpngtest.cc b/tests/cgalpngtest.cc index 41e7eaf9..734f6eb1 100644 --- a/tests/cgalpngtest.cc +++ b/tests/cgalpngtest.cc @@ -223,7 +223,7 @@ int main(int argc, char **argv) if (cgalRenderer.polyhedron) { CGAL::Bbox_3 cgalbbox = cgalRenderer.polyhedron->bbox(); bbox = BoundingBox(Vector3d(cgalbbox.xmin(), cgalbbox.ymin(), cgalbbox.zmin()), - Vector3d(cgalbbox.xmax(), cgalbbox.ymax(), cgalbbox.zmax())); + Vector3d(cgalbbox.xmax(), cgalbbox.ymax(), cgalbbox.zmax())); } else if (cgalRenderer.polyset) { bbox = cgalRenderer.polyset->getBoundingBox(); @@ -246,4 +246,3 @@ int main(int argc, char **argv) return 0; } - From dea923c84761ccfbd210ea3dea9c90b5784a9b85 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 16 Oct 2011 18:54:18 +0200 Subject: [PATCH 47/93] M_PI define for Windows --- tests/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 76a08ec0..c4d1b7f6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -40,6 +40,8 @@ if(WIN32) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4100") # fopen_s advertisement set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_CRT_SECURE_NO_DEPRECATE") + # M_PI + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_USE_MATH_DEFINES") endif() # Debugging - if you uncomment, use nmake -f Makefile > log.txt (the log is big) From 45044b7907b1f3b8cee043bf6893cb96684ce398 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 16 Oct 2011 18:54:58 +0200 Subject: [PATCH 48/93] M_PI for Windows should now be turned on as default --- src/func.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/func.cc b/src/func.cc index 75e90dfb..b011a27f 100644 --- a/src/func.cc +++ b/src/func.cc @@ -34,7 +34,6 @@ #include #include "stl-utils.h" #include -#include "openscad.h" //M_PI AbstractFunction::~AbstractFunction() { From 7bdd95ab97dc170f8ec97ce0702391ec2af1159e Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 16 Oct 2011 18:55:05 +0200 Subject: [PATCH 49/93] M_PI for Windows should now be turned on as default --- src/openscad.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/openscad.h b/src/openscad.h index e6b9b9f2..61aec0ef 100644 --- a/src/openscad.h +++ b/src/openscad.h @@ -27,12 +27,6 @@ #ifndef OPENSCAD_H #define OPENSCAD_H -// for win32 and maybe others.. -#ifndef M_PI -# define M_PI 3.14159265358979323846 -#endif - - extern class AbstractModule *parse(const char *text, const char *path, int debug); extern int get_fragments_from_r(double r, double fn, double fs, double fa); From c9b95ff06b8a435a0fb7bc44f569553f58db0835 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 16 Oct 2011 18:55:35 +0200 Subject: [PATCH 50/93] FIXME for reenabling -frounding-math for Windows --- cgal.pri | 1 + 1 file changed, 1 insertion(+) diff --git a/cgal.pri b/cgal.pri index 36afa486..05ec6f45 100644 --- a/cgal.pri +++ b/cgal.pri @@ -17,6 +17,7 @@ cgal { LIBS += $$CGAL_DIR/auxiliary/gmp/lib/libmpfr-4.lib -lCGAL-vc90-mt-s } else { LIBS += -lgmp -lmpfr -lCGAL + # FIXME: We should put this back for the Windows gcc-build QMAKE_CXXFLAGS += -frounding-math # visual C++ doesn't have this } From b12b0861756d99c876a243ad5bda85da1a066ac5 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 16 Oct 2011 18:56:08 +0200 Subject: [PATCH 51/93] cleanup: use -DEIGEN_DONT_ALIGN for all except Mac --- openscad.pro | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openscad.pro b/openscad.pro index 49553ada..994a5277 100644 --- a/openscad.pro +++ b/openscad.pro @@ -55,8 +55,7 @@ win32:QMAKE_CXXFLAGS += -wd4800 win32:QMAKE_CXXFLAGS += -wd4100 # disable Eigen SIMD optimizations for non-Mac OSX -win32:QMAKE_CXXFLAGS += -DEIGEN_DONT_ALIGN -unix:QMAKE_CXXFLAGS += -DEIGEN_DONT_ALIGN +!macx:QMAKE_CXXFLAGS += -DEIGEN_DONT_ALIGN TEMPLATE = app RESOURCES = openscad.qrc From f4ee7e90d8a4f917ccdf19b732377f37579dab53 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 16 Oct 2011 22:50:29 +0200 Subject: [PATCH 52/93] Linux cmake fixes --- tests/CMakeLists.txt | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c4d1b7f6..d3abff22 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,7 +1,9 @@ cmake_minimum_required(VERSION 2.8) -# Explicitly use new include policy to avoid globally shadowing included modules -# http://www.cmake.org/cmake/help/cmake-2-8-docs.html#policy:CMP0017 -cmake_policy(SET CMP0017 NEW) +if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}" GREATER 2.8.3) + # Explicitly use new include policy to avoid globally shadowing included modules + # http://www.cmake.org/cmake/help/cmake-2-8-docs.html#policy:CMP0017 + cmake_policy(SET CMP0017 NEW) +endif() project(tests) set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}") @@ -190,18 +192,6 @@ set(COMMON_SOURCES add_executable(echotest echotest.cc ${CORE_SOURCES}) target_link_libraries(echotest ${QT_LIBRARIES} ${OPENGL_LIBRARY}) -# -# echotest -# -add_executable(echotest echotest.cc ${CORE_SOURCES}) -target_link_libraries(echotest ${QT_LIBRARIES} ${OPENGL_LIBRARY}) - -# -# echotest -# -add_executable(echotest echotest.cc ${CORE_SOURCES}) -target_link_libraries(echotest ${QT_LIBRARIES} ${OPENGL_LIBRARY}) - # # Offscreen OpenGL context source code # From 1a1ab5b5ab926652293d509b0fd4d7f57451c794 Mon Sep 17 00:00:00 2001 From: don b Date: Mon, 17 Oct 2011 23:16:02 -0500 Subject: [PATCH 53/93] improve freebsd build. also remove windows unicode preamble from .cc --- eigen2.pri | 4 +++- openscad.pro | 4 ++++ tests/CMakeLists.txt | 15 ++++++++++++++- tests/OffscreenContext.cc | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/eigen2.pri b/eigen2.pri index 8344f006..eba53357 100644 --- a/eigen2.pri +++ b/eigen2.pri @@ -5,7 +5,9 @@ EIGEN2_DIR = $$(EIGEN2DIR) INCLUDEPATH += $$EIGEN2_DIR } else { - macx { + unix:freebsd-g++ { + INCLUDEPATH += /usr/local/include/eigen2 + } else macx { INCLUDEPATH += /opt/local/include/eigen2 } else { diff --git a/openscad.pro b/openscad.pro index 994a5277..ff651622 100644 --- a/openscad.pro +++ b/openscad.pro @@ -25,6 +25,10 @@ VERSION_MONTH=$$member(VERSION_SPLIT, 1) VERSION_DAY=$$member(VERSION_SPLIT, 2) #configure lex / yacc +unix:freebsd-g++ { + QMAKE_LEX = /usr/local/bin/flex + QMAKE_YACC = /usr/local/bin/bison +} win32 { include(flex.pri) include(bison.pri) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d3abff22..2ed625a2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -74,13 +74,20 @@ include(${QT_USE_FILE}) # Turn off Eigen SIMD optimization if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEIGEN_DONT_ALIGN") + if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEIGEN_DONT_ALIGN") + endif() endif() if (NOT EIGEN2_INCLUDE_DIR) find_path(EIGEN2_INCLUDE_DIR Eigen/Core PATHS ENV EIGEN2DIR /opt/local/include/eigen2 /usr/include/eigen2) + if (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") + find_path(EIGEN2_INCLUDE_DIR + Eigen/Core + PATHS ENV EIGEN2DIR /usr/local/include/eigen2 ) + endif() if (NOT EIGEN2_INCLUDE_DIR) message(FATAL_ERROR "Eigen2 not found") else() @@ -126,6 +133,12 @@ endif() # Flex/Bison find_package(BISON) + +if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") + # FreeBSD has an old flex in /usr/bin and a new flex in /usr/local/bin + set(FLEX_EXECUTABLE /usr/local/bin/flex) +endif() + find_package(FLEX) # The COMPILE_FLAGS and forced C++ compiler is just to be compatible with qmake if (WIN32) diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc index 06799627..2474228c 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContext.cc @@ -1,4 +1,4 @@ -/* +/* Create an OpenGL context without creating an OpenGL Window. for Linux. From 40b1b4ac278055eba6887f2ece721e0b6438b8b3 Mon Sep 17 00:00:00 2001 From: Don Bright Date: Tue, 18 Oct 2011 20:55:42 -0500 Subject: [PATCH 54/93] improve OpenGL error reporting. tracking down INVALID_ENUM bug --- src/CSGTermEvaluator.cc | 1 + tests/CMakeLists.txt | 4 ++-- tests/OffscreenContext.cc | 41 ++++++++++++--------------------- tests/fbo.cc | 35 ++++++++++++---------------- tests/system-gl.cc | 48 +++++++++++++++++++++++++++++++++++++++ tests/system-gl.h | 3 +++ 6 files changed, 82 insertions(+), 50 deletions(-) create mode 100644 tests/system-gl.cc diff --git a/src/CSGTermEvaluator.cc b/src/CSGTermEvaluator.cc index d1af987b..0c7bca70 100644 --- a/src/CSGTermEvaluator.cc +++ b/src/CSGTermEvaluator.cc @@ -17,6 +17,7 @@ #include #include #include +#include /*! \class CSGTermEvaluator diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d3abff22..1a732547 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -276,11 +276,11 @@ target_link_libraries(opencsgtest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${ # throwntogethertest # -add_executable(throwntogethertest throwntogethertest.cc csgtestcore.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc fbo.cc +add_executable(throwntogethertest throwntogethertest.cc csgtestcore.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc fbo.cc system-gl.cc ../src/OpenCSGRenderer.cc ../src/ThrownTogetherRenderer.cc ../src/CSGTermEvaluator.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc - ../src/CGAL_Nef_polyhedron_DxfData.cc ../src/cgaladv_minkowski2.cc ../src/cgaladv_convexhull2.cc + ../src/CGAL_Nef_polyhedron_DxfData.cc ../src/cgaladv_minkowski2.cc ../src/cgaladv_convexhull2.cc ${COMMON_SOURCES}) set_target_properties(throwntogethertest PROPERTIES COMPILE_FLAGS "-DENABLE_OPENCSG -DENABLE_CGAL ${CGAL_CXX_FLAGS_INIT}") target_link_libraries(throwntogethertest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${QT_LIBRARIES} ${OPENCSG_LIBRARY} ${GLEW_LIBRARY} ${COCOA_LIBRARY} ${OPENGL_LIBRARY}) diff --git a/tests/OffscreenContext.cc b/tests/OffscreenContext.cc index 06799627..727e7711 100644 --- a/tests/OffscreenContext.cc +++ b/tests/OffscreenContext.cc @@ -16,6 +16,7 @@ See Also #include "OffscreenContext.h" #include "printutils.h" #include "imageutils.h" +#include "system-gl.h" #include "fbo.h" #include @@ -98,15 +99,16 @@ bool create_glx_dummy_window(OffscreenContext &ctx) // can't depend on xWin==NULL at failure. use a custom Xlib error handler instead. original_xlib_handler = XSetErrorHandler( XCreateWindow_error ); Window xWin = XCreateSimpleWindow( dpy, DefaultRootWindow(dpy), 0,0,10,10, 0,0,0 ); - XSync( dpy, false ); - if ( XCreateWindow_failed ) { + XSync( dpy, false ); + if ( XCreateWindow_failed ) { XFree( visinfo ); XFree( fbconfigs ); - return false; + return false; } XSetErrorHandler( original_xlib_handler ); // Most programs would call XMapWindow here. But we don't, to keep the window hidden + XMapWindow( dpy, xWin ); GLXContext context = glXCreateNewContext( dpy, fbconfigs[0], GLX_RGBA_TYPE, NULL, True ); if ( context == NULL ) { @@ -115,9 +117,12 @@ bool create_glx_dummy_window(OffscreenContext &ctx) XFree( visinfo ); XFree( fbconfigs ); return false; - } + } - if (!glXMakeContextCurrent( dpy, xWin, xWin, context )) { + GLXWindow glxWin = glXCreateWindow( dpy, fbconfigs[0], xWin, NULL ); + + //if (!glXMakeContextCurrent( dpy, xWin, xWin, context )) { + if (!glXMakeContextCurrent( dpy, glxWin, glxWin, context )) { cerr << "glXMakeContextCurrent failed\n"; glXDestroyContext( dpy, context ); XDestroyWindow( dpy, xWin ); @@ -165,25 +170,6 @@ Bool create_glx_dummy_context(OffscreenContext &ctx) return result; } -void glewCheck() { -#ifdef DEBUG - cerr << "GLEW version " << glewGetString(GLEW_VERSION) << "\n"; - cerr << (const char *)glGetString(GL_RENDERER) << "(" << (const char *)glGetString(GL_VENDOR) << ")\n" - << "OpenGL version " << (const char *)glGetString(GL_VERSION) << "\n"; - cerr << "Extensions: " << (const char *)glGetString(GL_EXTENSIONS) << "\n"; - - if (GLEW_ARB_framebuffer_object) { - cerr << "ARB_FBO supported\n"; - } - if (GLEW_EXT_framebuffer_object) { - cerr << "EXT_FBO supported\n"; - } - if (GLEW_EXT_packed_depth_stencil) { - cerr << "EXT_packed_depth_stencil\n"; - } -#endif -} - OffscreenContext *create_offscreen_context(int w, int h) { OffscreenContext *ctx = new OffscreenContext; @@ -199,13 +185,14 @@ OffscreenContext *create_offscreen_context(int w, int h) // glewInit must come after Context creation and before FBO calls. GLenum err = glewInit(); if (GLEW_OK != err) { - fprintf(stderr, "Unable to init GLEW: %s\n", glewGetErrorString(err)); - exit(1); + cerr << "Unable to init GLEW: " << glewGetErrorString(err) << endl; + return NULL; } - glewCheck(); ctx->fbo = fbo_new(); if (!fbo_init(ctx->fbo, w, h)) { + cerr << "Framebuffer init failed; dumping GLEW info" << endl; + glew_dump(); return NULL; } diff --git a/tests/fbo.cc b/tests/fbo.cc index f3d92236..42dacaa3 100644 --- a/tests/fbo.cc +++ b/tests/fbo.cc @@ -1,4 +1,5 @@ #include "fbo.h" +#include "system-gl.h" #include #include using namespace std; @@ -14,16 +15,6 @@ fbo_t *fbo_new() return fbo; } -bool REPORTGLERROR(const char * task) -{ - GLenum tGLErr = glGetError(); - if (tGLErr != GL_NO_ERROR) { - std::cerr << "OpenGL error " << tGLErr << " while " << task << "\n"; - return true; - } else - return false; -} - bool use_ext() { // do we need to use the EXT or ARB version? @@ -47,7 +38,7 @@ bool check_fbo_status() else status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (REPORTGLERROR("checking framebuffer status")) return false; + if (report_glerror("checking framebuffer status")) return false; if (status == GL_FRAMEBUFFER_COMPLETE) result = true; @@ -76,8 +67,9 @@ bool fbo_ext_init(fbo_t *fbo, size_t width, size_t height) { // Generate and bind FBO glGenFramebuffersEXT(1, &fbo->fbo_id); + if (report_glerror("glGenFramebuffersEXT")) return false; glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->fbo_id); - if (REPORTGLERROR("binding framebuffer")) return false; + if (report_glerror("glBindFramebufferEXT")) return false; // Generate depth and render buffers glGenRenderbuffersEXT(1, &fbo->depthbuf_id); @@ -89,7 +81,7 @@ bool fbo_ext_init(fbo_t *fbo, size_t width, size_t height) // Attach render and depth buffers glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, fbo->renderbuf_id); - if (REPORTGLERROR("specifying color render buffer")) return false; + if (report_glerror("specifying color render buffer")) return false; if (!check_fbo_status()) { @@ -99,7 +91,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 (REPORTGLERROR("specifying depth render buffer")) return false; + if (report_glerror("specifying depth render buffer")) return false; if (!check_fbo_status()) { cerr << "Problem with OpenGL framebuffer after specifying depth render buffer.\n"; @@ -113,8 +105,9 @@ bool fbo_arb_init(fbo_t *fbo, size_t width, size_t height) { // Generate and bind FBO glGenFramebuffers(1, &fbo->fbo_id); + if (report_glerror("glGenFramebuffers")) return false; glBindFramebuffer(GL_FRAMEBUFFER, fbo->fbo_id); - if (REPORTGLERROR("binding framebuffer")) return false; + if (report_glerror("glBindFramebuffer")) return false; // Generate depth and render buffers glGenRenderbuffers(1, &fbo->depthbuf_id); @@ -126,7 +119,7 @@ bool fbo_arb_init(fbo_t *fbo, size_t width, size_t height) // Attach render and depth buffers glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fbo->renderbuf_id); - if (REPORTGLERROR("specifying color render buffer")) return false; + if (report_glerror("specifying color render buffer")) return false; if (!check_fbo_status()) { cerr << "Problem with OpenGL framebuffer after specifying color render buffer.\n"; @@ -135,7 +128,7 @@ bool fbo_arb_init(fbo_t *fbo, size_t width, size_t height) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fbo->depthbuf_id); - if (REPORTGLERROR("specifying depth render buffer")) return false; + if (report_glerror("specifying depth render buffer")) return false; if (!check_fbo_status()) { cerr << "Problem with OpenGL framebuffer after specifying depth render buffer.\n"; @@ -173,19 +166,19 @@ bool fbo_resize(fbo_t *fbo, size_t width, size_t height) if (use_ext()) { glBindRenderbufferEXT(GL_RENDERBUFFER, fbo->depthbuf_id); glRenderbufferStorageEXT(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); - if (REPORTGLERROR("creating depth render buffer")) return false; + if (report_glerror("creating depth render buffer")) return false; glBindRenderbufferEXT(GL_RENDERBUFFER, fbo->renderbuf_id); glRenderbufferStorageEXT(GL_RENDERBUFFER, GL_RGBA8, width, height); - if (REPORTGLERROR("creating color render buffer")) return false; + if (report_glerror("creating color render buffer")) return false; } else { glBindRenderbuffer(GL_RENDERBUFFER, fbo->depthbuf_id); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); - if (REPORTGLERROR("creating depth render buffer")) return false; + if (report_glerror("creating depth render buffer")) return false; glBindRenderbuffer(GL_RENDERBUFFER, fbo->renderbuf_id); glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height); - if (REPORTGLERROR("creating color render buffer")) return false; + if (report_glerror("creating color render buffer")) return false; } return true; diff --git a/tests/system-gl.cc b/tests/system-gl.cc new file mode 100644 index 00000000..b7c7dd62 --- /dev/null +++ b/tests/system-gl.cc @@ -0,0 +1,48 @@ + +/* OpenGL helper functions */ + +#include +#include "system-gl.h" +#include + +using namespace std; +using namespace boost; + +void glew_dump() { + 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 extensions((const char *)glGetString(GL_EXTENSIONS)); + replace_all( extensions, " ", "\n " ); + cerr << "Extensions: " << endl << " " << extensions << endl + << "Framebuffer and Stencil support:" << endl + << " GLEW_ARB_framebuffer_object: " << (bool)GLEW_ARB_framebuffer_object << endl + << " GLEW_EXT_framebuffer_object: " << (bool)GLEW_EXT_framebuffer_object << endl + << " GLEW_EXT_packed_depth_stencil: " << (bool)GLEW_EXT_packed_depth_stencil << endl + << " glewIsSupported(\"GL_ARB_framebuffer_object\"): " << (bool)glewIsSupported("GL_ARB_framebuffer_object") << endl + << " glewIsSupported(\"GL_EXT_framebuffer_object\"): " << (bool)glewIsSupported("GL_EXT_framebuffer_object") << endl + << " glewIsSupported(\"GL_EXT_packed_depth_stencil\"): " << (bool)glewIsSupported("GL_EXT_packed_depth_stencil") << endl; +}; + +const char * gl_errors[] = { + "​GL_INVALID_ENUM​", // 0x0500 + "GL_INVALID_VALUE", // 0x0501 + "GL_INVALID_OPERATION", // 0x0502 + "GL_OUT_OF_MEMORY" // 0x0503 +}; + +bool report_glerror(const char * task) +{ + GLenum tGLErr = glGetError(); + if (tGLErr != GL_NO_ERROR) { + if ( (tGLErr-0x500)<=3 && (tGLErr-0x500)>=0 ) + cerr << "OpenGL error " << gl_errors[tGLErr-0x500] << " while " << task << endl; + else + cerr << "OpenGL error " << hex << tGLErr << " while " << task << endl; + return true; + } + return false; +} + diff --git a/tests/system-gl.h b/tests/system-gl.h index 0f983cfb..bb41be57 100644 --- a/tests/system-gl.h +++ b/tests/system-gl.h @@ -3,4 +3,7 @@ #include +void glew_dump(); +bool report_glerror(const char *task); + #endif From 038767f6a601399d2e6792019f2ae2c326e04bec Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Wed, 19 Oct 2011 17:35:14 +0200 Subject: [PATCH 55/93] Experimental support for exporting the compiled CSG tree as a text file --- src/MainWindow.h | 1 + src/MainWindow.ui | 6 ++ src/mainwin.cc | 33 ++++++++++ src/openscad.cc | 156 +++++++++++++++++++++++++--------------------- 4 files changed, 124 insertions(+), 72 deletions(-) diff --git a/src/MainWindow.h b/src/MainWindow.h index 37a6a4cd..a3c812b9 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -115,6 +115,7 @@ private slots: void actionExportSTL(); void actionExportOFF(); void actionExportDXF(); + void actionExportCSG(); void actionExportImage(); void actionFlushCaches(); diff --git a/src/MainWindow.ui b/src/MainWindow.ui index 17415573..4d5ff22a 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -178,6 +178,7 @@ + @@ -657,6 +658,11 @@ Export as Image... + + + Export as CSG... + + diff --git a/src/mainwin.cc b/src/mainwin.cc index 238bd107..be82d263 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -283,6 +283,7 @@ MainWindow::MainWindow(const QString &filename) connect(this->designActionExportSTL, SIGNAL(triggered()), this, SLOT(actionExportSTL())); connect(this->designActionExportOFF, SIGNAL(triggered()), this, SLOT(actionExportOFF())); connect(this->designActionExportDXF, SIGNAL(triggered()), this, SLOT(actionExportDXF())); + connect(this->designActionExportCSG, SIGNAL(triggered()), this, SLOT(actionExportCSG())); connect(this->designActionExportImage, SIGNAL(triggered()), this, SLOT(actionExportImage())); connect(this->designActionFlushCaches, SIGNAL(triggered()), this, SLOT(actionFlushCaches())); @@ -1474,6 +1475,38 @@ void MainWindow::actionExportDXF() #endif /* ENABLE_CGAL */ } +void MainWindow::actionExportCSG() +{ + setCurrentOutput(); + + if (!this->root_node) { + PRINT("Nothing to export. Please try compiling first..."); + clearCurrentOutput(); + return; + } + + QString csg_filename = QFileDialog::getSaveFileName(this, "Export CSG File", + this->fileName.isEmpty() ? "Untitled.csg" : QFileInfo(this->fileName).baseName()+".csg", + "CSG Files (*.csg)"); + if (csg_filename.isEmpty()) { + PRINTF("No filename specified. CSG export aborted."); + clearCurrentOutput(); + return; + } + + std::ofstream fstream(csg_filename.toUtf8()); + if (!fstream.is_open()) { + PRINTA("Can't open file \"%s\" for export", csg_filename); + } + else { + fstream << this->tree.getString(*this->root_node) << "\n"; + fstream.close(); + PRINTF("CSG export finished."); + } + + clearCurrentOutput(); +} + void MainWindow::actionExportImage() { QImage img = this->glview->grabFrameBuffer(); diff --git a/src/openscad.cc b/src/openscad.cc index 878cb229..f3d28a60 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -70,7 +70,7 @@ namespace po = boost::program_options; static void help(const char *progname) { - fprintf(stderr, "Usage: %s [ { -s stl_file | -o off_file | -x dxf_file } [ -d deps_file ] ]\\\n" + fprintf(stderr, "Usage: %s [ { -o output_file } [ -d deps_file ] ]\\\n" "%*s[ -m make_command ] [ -D var=val [..] ] filename\n", progname, int(strlen(progname))+8, ""); exit(1); @@ -126,18 +126,14 @@ int main(int argc, char **argv) QCoreApplication::setApplicationName("OpenSCAD"); const char *filename = NULL; - const char *stl_output_file = NULL; - const char *off_output_file = NULL; - const char *dxf_output_file = NULL; + const char *output_file = NULL; const char *deps_output_file = NULL; po::options_description desc("Allowed options"); desc.add_options() ("help,h", "help message") ("version,v", "print the version") - ("s,s", po::value(), "stl-file") - ("o,o", po::value(), "off-file") - ("x,x", po::value(), "dxf-file") + ("o,o", po::value(), "out-file") ("d,d", po::value(), "deps-file") ("m,m", po::value(), "makefile") ("D,D", po::value >(), "var=val"); @@ -159,20 +155,10 @@ int main(int argc, char **argv) if (vm.count("help")) help(argv[0]); if (vm.count("version")) version(); - if (vm.count("s")) { - if (stl_output_file || off_output_file || dxf_output_file) - help(argv[0]); - stl_output_file = vm["s"].as().c_str(); - } if (vm.count("o")) { - if (stl_output_file || off_output_file || dxf_output_file) - help(argv[0]); - off_output_file = vm["o"].as().c_str(); - } - if (vm.count("x")) { - if (stl_output_file || off_output_file || dxf_output_file) - help(argv[0]); - dxf_output_file = vm["x"].as().c_str(); + // FIXME: Allow for multiple output files? + if (output_file) help(argv[0]); + output_file = vm["o"].as().c_str(); } if (vm.count("d")) { if (deps_output_file) @@ -253,10 +239,24 @@ int main(int argc, char **argv) PolySetCGALEvaluator psevaluator(cgalevaluator); #endif - if (stl_output_file || off_output_file || dxf_output_file) + if (output_file) { - if (!filename) - help(argv[0]); + const char *stl_output_file = NULL; + const char *off_output_file = NULL; + const char *dxf_output_file = NULL; + const char *csg_output_file = NULL; + + QString suffix = QFileInfo(output_file).suffix().toLower(); + if (suffix == "stl") stl_output_file = output_file; + else if (suffix == "off") off_output_file = output_file; + else if (suffix == "dxf") dxf_output_file = output_file; + else if (suffix == "csg") csg_output_file = output_file; + else { + fprintf(stderr, "Unknown suffix for output file %s\n", output_file); + exit(1); + } + + if (!filename) help(argv[0]); #ifdef ENABLE_CGAL Context root_ctx; @@ -304,68 +304,80 @@ int main(int argc, char **argv) AbstractNode::resetIndexCounter(); root_node = root_module->evaluate(&root_ctx, &root_inst); - tree.setRoot(root_node); - CGAL_Nef_polyhedron root_N = cgalevaluator.evaluateCGALMesh(*tree.root()); - QDir::setCurrent(original_path.absolutePath()); - - if (deps_output_file) { - if (!write_deps(deps_output_file, - stl_output_file ? stl_output_file : off_output_file)) { - exit(1); - } - } - - if (stl_output_file) { - if (root_N.dim != 3) { - fprintf(stderr, "Current top level object is not a 3D object.\n"); - exit(1); - } - if (!root_N.p3->is_simple()) { - fprintf(stderr, "Object isn't a valid 2-manifold! Modify your design.\n"); - exit(1); - } - std::ofstream fstream(stl_output_file); + if (csg_output_file) { + QDir::setCurrent(original_path.absolutePath()); + std::ofstream fstream(csg_output_file); if (!fstream.is_open()) { - PRINTF("Can't open file \"%s\" for export", stl_output_file); + PRINTF("Can't open file \"%s\" for export", csg_output_file); } else { - export_stl(&root_N, fstream, NULL); + fstream << tree.getString(*root_node) << "\n"; fstream.close(); } } + else { + CGAL_Nef_polyhedron root_N = cgalevaluator.evaluateCGALMesh(*tree.root()); + + QDir::setCurrent(original_path.absolutePath()); + + if (deps_output_file) { + if (!write_deps(deps_output_file, + stl_output_file ? stl_output_file : off_output_file)) { + exit(1); + } + } - if (off_output_file) { - if (root_N.dim != 3) { - fprintf(stderr, "Current top level object is not a 3D object.\n"); - exit(1); + if (stl_output_file) { + if (root_N.dim != 3) { + fprintf(stderr, "Current top level object is not a 3D object.\n"); + exit(1); + } + if (!root_N.p3->is_simple()) { + fprintf(stderr, "Object isn't a valid 2-manifold! Modify your design.\n"); + exit(1); + } + std::ofstream fstream(stl_output_file); + if (!fstream.is_open()) { + PRINTF("Can't open file \"%s\" for export", stl_output_file); + } + else { + export_stl(&root_N, fstream, NULL); + fstream.close(); + } } - if (!root_N.p3->is_simple()) { - fprintf(stderr, "Object isn't a valid 2-manifold! Modify your design.\n"); - exit(1); + + if (off_output_file) { + if (root_N.dim != 3) { + fprintf(stderr, "Current top level object is not a 3D object.\n"); + exit(1); + } + if (!root_N.p3->is_simple()) { + fprintf(stderr, "Object isn't a valid 2-manifold! Modify your design.\n"); + exit(1); + } + std::ofstream fstream(off_output_file); + if (!fstream.is_open()) { + PRINTF("Can't open file \"%s\" for export", off_output_file); + } + else { + export_off(&root_N, fstream, NULL); + fstream.close(); + } } - std::ofstream fstream(off_output_file); - if (!fstream.is_open()) { - PRINTF("Can't open file \"%s\" for export", off_output_file); - } - else { - export_off(&root_N, fstream, NULL); - fstream.close(); + + if (dxf_output_file) { + std::ofstream fstream(dxf_output_file); + if (!fstream.is_open()) { + PRINTF("Can't open file \"%s\" for export", dxf_output_file); + } + else { + export_dxf(&root_N, fstream, NULL); + fstream.close(); + } } } - - if (dxf_output_file) { - std::ofstream fstream(dxf_output_file); - if (!fstream.is_open()) { - PRINTF("Can't open file \"%s\" for export", dxf_output_file); - } - else { - export_dxf(&root_N, fstream, NULL); - fstream.close(); - } - } - delete root_node; #else fprintf(stderr, "OpenSCAD has been compiled without CGAL support!\n"); From 4799b7b555b3ddf86f9949caf9aae0e706cea4bc Mon Sep 17 00:00:00 2001 From: Don Bright Date: Wed, 19 Oct 2011 17:30:19 -0500 Subject: [PATCH 56/93] always dump hex code of GL error --- tests/system-gl.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/system-gl.cc b/tests/system-gl.cc index b7c7dd62..750237dc 100644 --- a/tests/system-gl.cc +++ b/tests/system-gl.cc @@ -27,7 +27,7 @@ void glew_dump() { }; const char * gl_errors[] = { - "​GL_INVALID_ENUM​", // 0x0500 + "GL_INVALID_ENUM", // 0x0500 "GL_INVALID_VALUE", // 0x0501 "GL_INVALID_OPERATION", // 0x0502 "GL_OUT_OF_MEMORY" // 0x0503 @@ -38,7 +38,8 @@ bool report_glerror(const char * task) GLenum tGLErr = glGetError(); if (tGLErr != GL_NO_ERROR) { if ( (tGLErr-0x500)<=3 && (tGLErr-0x500)>=0 ) - cerr << "OpenGL error " << gl_errors[tGLErr-0x500] << " while " << task << endl; + cerr << "OpenGL error " << hex << tGLErr << "(" << + << gl_errors[tGLErr-0x500] << ") while " << task << endl; else cerr << "OpenGL error " << hex << tGLErr << " while " << task << endl; return true; From c7a809e0fd653795a93642301bbff8a27581ea57 Mon Sep 17 00:00:00 2001 From: don Date: Wed, 19 Oct 2011 19:37:00 -0500 Subject: [PATCH 57/93] fix broken build --- tests/CMakeLists.txt | 6 +++--- tests/FindGLEW.cmake | 2 +- tests/system-gl.cc | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 40286ddc..1fca2fe7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -253,7 +253,7 @@ include_directories(${CGAL_INCLUDE_DIRS}) # # cgaltest # -add_executable(cgaltest cgaltest.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CSGTermEvaluator.cc +add_executable(cgaltest cgaltest.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CSGTermEvaluator.cc system-gl.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc ../src/CGAL_Nef_polyhedron_DxfData.cc ../src/cgaladv_minkowski2.cc ../src/cgaladv_convexhull2.cc ${COMMON_SOURCES}) @@ -263,7 +263,7 @@ target_link_libraries(cgaltest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${QT_ # # cgalpngtest # -add_executable(cgalpngtest cgalpngtest.cc OffscreenView.cc bboxhelp.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc fbo.cc +add_executable(cgalpngtest cgalpngtest.cc OffscreenView.cc bboxhelp.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc fbo.cc system-gl.cc ../src/CGALRenderer.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CSGTermEvaluator.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc @@ -276,7 +276,7 @@ target_link_libraries(cgalpngtest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${ # opencsgtest # -add_executable(opencsgtest opencsgtest.cc csgtestcore.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc fbo.cc +add_executable(opencsgtest opencsgtest.cc csgtestcore.cc OffscreenView.cc ${OFFSCREEN_CTX_SOURCE} imageutils.cc fbo.cc system-gl.cc ../src/OpenCSGRenderer.cc ../src/ThrownTogetherRenderer.cc ../src/CSGTermEvaluator.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc diff --git a/tests/FindGLEW.cmake b/tests/FindGLEW.cmake index f49d5463..bd0669d3 100644 --- a/tests/FindGLEW.cmake +++ b/tests/FindGLEW.cmake @@ -30,7 +30,7 @@ IF (WIN32) ${PROJECT_SOURCE_DIR}/src/nvgl/glew/lib DOC "The GLEW library") ELSE (WIN32) - message("GLEW_DIR: " ${GLEW_DIR}) + message("-- GLEW_DIR: " ${GLEW_DIR}) FIND_PATH( GLEW_INCLUDE_PATH GL/glew.h PATHS ${GLEW_DIR}/include /usr/include /usr/local/include NO_DEFAULT_PATH diff --git a/tests/system-gl.cc b/tests/system-gl.cc index 00108cdf..88746626 100644 --- a/tests/system-gl.cc +++ b/tests/system-gl.cc @@ -38,8 +38,8 @@ bool report_glerror(const char * function) GLenum tGLErr = glGetError(); if (tGLErr != GL_NO_ERROR) { if ( (tGLErr-0x500)<=3 && (tGLErr-0x500)>=0 ) - cerr << "OpenGL error " << hex << tGLErr << "(" << - << gl_errors[tGLErr-0x500] << ") while " << task << endl; + cerr << "OpenGL error " << hex << tGLErr << "(" + << gl_errors[tGLErr-0x500] << ") while " << function << endl; else cerr << "OpenGL error 0x" << hex << tGLErr << " after " << function << endl; return true; From 6edeb13595a9236c6030bedefc14c62ba27e6675 Mon Sep 17 00:00:00 2001 From: don Date: Wed, 19 Oct 2011 19:57:34 -0500 Subject: [PATCH 58/93] cleanup error report --- tests/system-gl.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/system-gl.cc b/tests/system-gl.cc index 88746626..70002215 100644 --- a/tests/system-gl.cc +++ b/tests/system-gl.cc @@ -38,8 +38,8 @@ bool report_glerror(const char * function) GLenum tGLErr = glGetError(); if (tGLErr != GL_NO_ERROR) { if ( (tGLErr-0x500)<=3 && (tGLErr-0x500)>=0 ) - cerr << "OpenGL error " << hex << tGLErr << "(" - << gl_errors[tGLErr-0x500] << ") while " << function << endl; + cerr << "OpenGL error 0x" << hex << tGLErr << " (" + << gl_errors[tGLErr-0x500] << ") after " << function << endl; else cerr << "OpenGL error 0x" << hex << tGLErr << " after " << function << endl; return true; From 98530546c92294c6effb4b88b12bd3adc86d7c40 Mon Sep 17 00:00:00 2001 From: User Date: Thu, 20 Oct 2011 06:49:52 -0500 Subject: [PATCH 59/93] fix freebsd build --- openscad.pro | 6 +++++- tests/CMakeLists.txt | 2 +- tests/OffscreenContextWGL.cc | 26 ++++---------------------- tests/system-gl.cc | 12 +++++++++--- 4 files changed, 19 insertions(+), 27 deletions(-) diff --git a/openscad.pro b/openscad.pro index ff651622..7408c5e7 100644 --- a/openscad.pro +++ b/openscad.pro @@ -59,7 +59,11 @@ win32:QMAKE_CXXFLAGS += -wd4800 win32:QMAKE_CXXFLAGS += -wd4100 # disable Eigen SIMD optimizations for non-Mac OSX -!macx:QMAKE_CXXFLAGS += -DEIGEN_DONT_ALIGN +!macx { + !unix:freebsd-g++ { + QMAKE_CXXFLAGS += -DEIGEN_DONT_ALIGN + } +} TEMPLATE = app RESOURCES = openscad.qrc diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1fca2fe7..36021c0a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -253,7 +253,7 @@ include_directories(${CGAL_INCLUDE_DIRS}) # # cgaltest # -add_executable(cgaltest cgaltest.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CSGTermEvaluator.cc system-gl.cc +add_executable(cgaltest cgaltest.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc ../src/CSGTermEvaluator.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/PolySetCGALEvaluator.cc ../src/qhash.cc ../src/CGAL_Nef_polyhedron_DxfData.cc ../src/cgaladv_minkowski2.cc ../src/cgaladv_convexhull2.cc ${COMMON_SOURCES}) diff --git a/tests/OffscreenContextWGL.cc b/tests/OffscreenContextWGL.cc index 7e976762..6e202a40 100644 --- a/tests/OffscreenContextWGL.cc +++ b/tests/OffscreenContextWGL.cc @@ -12,13 +12,14 @@ For more info: */ #include +#include +#include #include "OffscreenContext.h" #include "printutils.h" #include "imageutils.h" +#include "system-gl.h" #include "fbo.h" -#include -#include using namespace std; @@ -42,25 +43,6 @@ void offscreen_context_init(OffscreenContext &ctx, int width, int height) ctx.fbo = NULL; } -void glewCheck() { -#ifdef DEBUG - cerr << "GLEW version " << glewGetString(GLEW_VERSION) << "\n"; - cerr << (const char *)glGetString(GL_RENDERER) << "(" << (const char *)glGetString(GL_VENDOR) << ")\n" - << "OpenGL version " << (const char *)glGetString(GL_VERSION) << "\n"; - //cerr << "Extensions: " << (const char *)glGetString(GL_EXTENSIONS) << "\n"; - - if (GLEW_ARB_framebuffer_object) { - cerr << "ARB_FBO supported\n"; - } - if (GLEW_EXT_framebuffer_object) { - cerr << "EXT_FBO supported\n"; - } - if (GLEW_EXT_packed_depth_stencil) { - cerr << "EXT_packed_depth_stencil\n"; - } -#endif -} - LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { return DefWindowProc( hwnd, message, wparam, lparam ); @@ -159,7 +141,7 @@ OffscreenContext *create_offscreen_context(int w, int h) cerr << "Unable to init GLEW: " << glewGetErrorString(err) << "\n"; return NULL; } - glewCheck(); + glew_dump(); ctx->fbo = fbo_new(); if (!fbo_init(ctx->fbo, w, h)) { diff --git a/tests/system-gl.cc b/tests/system-gl.cc index 70002215..fe9bdce7 100644 --- a/tests/system-gl.cc +++ b/tests/system-gl.cc @@ -21,9 +21,15 @@ void glew_dump() { cerr << "Extensions: " << endl << " " << extensions << endl; } - cerr << " GLEW_ARB_framebuffer_object: " << ((GLEW_ARB_framebuffer_object==1) ? "yes" : "no" ) << endl - << " GLEW_EXT_framebuffer_object: " << ((GLEW_EXT_framebuffer_object==1) ? "yes" : "no") << endl - << " GLEW_EXT_packed_depth_stencil: " << ((GLEW_EXT_packed_depth_stencil==1) ? "yes" : "no") << 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; }; const char * gl_errors[] = { From 3647f2ebee7df8463ff6af77023e926729931a1a Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 20 Oct 2011 15:15:50 +0200 Subject: [PATCH 60/93] Changed cmd-line parameters for file output --- RELEASE_NOTES | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index e6472378..aaeddb11 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -27,6 +27,7 @@ o dxf_linear_extrude() and dxf_rotate_extrude() are now deprecated. o The file, layer, origin and scale parameters to linear_extrude() and rotate_extrude() are now deprecated. Use an import() child instead. o import_dxf(), import_stl() and import_off() are now deprecated. Use import() instead. +o When exporting geometry from the cmd-line, use the universal -o option. It will export to the correct file format based on the given suffix (dxf, stl, off). OpenSCAD 2011.06 ================ From 9718d88e50f30dcbb7f99a196150472b39e66885 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 20 Oct 2011 16:43:49 +0200 Subject: [PATCH 61/93] bugfix: cmake version check was broken --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 36021c0a..22d4f969 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 2.8) -if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}" GREATER 2.8.3) +if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}" VERSION_GREATER 2.8.3) # Explicitly use new include policy to avoid globally shadowing included modules # http://www.cmake.org/cmake/help/cmake-2-8-docs.html#policy:CMP0017 cmake_policy(SET CMP0017 NEW) From 803818e1065af3d459608c117af0132e82c39cb1 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 20 Oct 2011 18:53:04 +0200 Subject: [PATCH 62/93] bugfix: the freebsd changes broke the mac build. --- eigen2.pri | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/eigen2.pri b/eigen2.pri index eba53357..dac52f58 100644 --- a/eigen2.pri +++ b/eigen2.pri @@ -5,10 +5,12 @@ EIGEN2_DIR = $$(EIGEN2DIR) INCLUDEPATH += $$EIGEN2_DIR } else { - unix:freebsd-g++ { - INCLUDEPATH += /usr/local/include/eigen2 - } else macx { - INCLUDEPATH += /opt/local/include/eigen2 + unix { + freebsd-g++ { + INCLUDEPATH += /usr/local/include/eigen2 + } else { + macx: INCLUDEPATH += /opt/local/include/eigen2 + } } else { INCLUDEPATH += /usr/include/eigen2 From 3366403d1df0cdf4f633194e9d2564756381763c Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 20 Oct 2011 18:59:34 +0200 Subject: [PATCH 63/93] Linux compile fix: include cstddef for NULL --- src/CSGTermEvaluator.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CSGTermEvaluator.h b/src/CSGTermEvaluator.h index cca6c91b..3a8122b7 100644 --- a/src/CSGTermEvaluator.h +++ b/src/CSGTermEvaluator.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "visitor.h" class CSGTermEvaluator : public Visitor From b534f40aa0b01f1100854f3acbd35ec725792604 Mon Sep 17 00:00:00 2001 From: don Date: Fri, 21 Oct 2011 01:49:34 +0000 Subject: [PATCH 64/93] eigen align tweak --- openscad.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openscad.pro b/openscad.pro index 7408c5e7..027576d4 100644 --- a/openscad.pro +++ b/openscad.pro @@ -60,7 +60,7 @@ win32:QMAKE_CXXFLAGS += -wd4100 # disable Eigen SIMD optimizations for non-Mac OSX !macx { - !unix:freebsd-g++ { + !freebsd-g++ { QMAKE_CXXFLAGS += -DEIGEN_DONT_ALIGN } } From 44d1436d47e3cc43dbca9c0378af3cdb2d475297 Mon Sep 17 00:00:00 2001 From: don Date: Sat, 22 Oct 2011 13:38:53 +0000 Subject: [PATCH 65/93] fix no-rounding-math flag for windows G++ build in cgal.pri --- cgal.pri | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cgal.pri b/cgal.pri index 05ec6f45..c0650449 100644 --- a/cgal.pri +++ b/cgal.pri @@ -13,12 +13,14 @@ cgal { } } - win32 { + windows { + *-g++* { + QMAKE_CXXFLAGS += -frounding-math + } LIBS += $$CGAL_DIR/auxiliary/gmp/lib/libmpfr-4.lib -lCGAL-vc90-mt-s } else { LIBS += -lgmp -lmpfr -lCGAL - # FIXME: We should put this back for the Windows gcc-build - QMAKE_CXXFLAGS += -frounding-math # visual C++ doesn't have this + QMAKE_CXXFLAGS += -frounding-math } } From 7d9104e748aaf9f54a490a8ca143d2864ddb61d3 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 23 Oct 2011 22:33:11 +0200 Subject: [PATCH 66/93] Evaluate expressions for the ternary operator the same way as for if-else, i.e. cast any value to bool --- src/expr.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/expr.cc b/src/expr.cc index c9eda4e1..fc1fbf0e 100644 --- a/src/expr.cc +++ b/src/expr.cc @@ -75,9 +75,7 @@ Value Expression::evaluate(const Context *context) const return this->children[0]->evaluate(context) > this->children[1]->evaluate(context); if (this->type == "?:") { Value v = this->children[0]->evaluate(context); - if (v.type == Value::BOOL) - return this->children[v.b ? 1 : 2]->evaluate(context); - return Value(); + return this->children[v.toBool() ? 1 : 2]->evaluate(context); } if (this->type == "[]") { Value v1 = this->children[0]->evaluate(context); From 9afee60563930bd75fa559306bd997c81c622da8 Mon Sep 17 00:00:00 2001 From: don bright Date: Sun, 23 Oct 2011 20:14:35 -0500 Subject: [PATCH 67/93] auto detect date in windows qmake --- openscad.pro | 66 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/openscad.pro b/openscad.pro index 027576d4..f5c5449d 100644 --- a/openscad.pro +++ b/openscad.pro @@ -13,16 +13,64 @@ win32 { # QMAKE_LFLAGS += -VERBOSE } -win32 { - isEmpty(VERSION) VERSION = $$system(date /t) - # isEmpty(VERSION) VERSION = "2011.10.15" # for XP, set version manually -} else { - isEmpty(VERSION) VERSION = $$system(date "+%Y.%m.%d") +isEmpty(VERSION) { + win32 { + # + # Windows XP date command only has one argument, /t + # and it can print the date in various localized formats. + # This code will detect MM/DD/YYYY, YYYY/MM/DD, and DD/MM/YYYY + # + SYSDATE = $$system(date /t) + message("Reading date from Windows date.exe... " $$SYSDATE) + SYSDATE = $$replace(SYSDATE,"/",".") + SYSDATE ~= s/[A-Za-z]*// # remove name of day + DATE_SPLIT=$$split(SYSDATE, ".") + DATE_X=$$member(DATE_SPLIT, 0) + DATE_Y=$$member(DATE_SPLIT, 1) + DATE_Z=$$member(DATE_SPLIT, 2) + TEST1=$$find(DATE_X, [0-9]{4} ) + TEST2=$$find(DATE_Z, [0-9]{4} ) + + QDATE = $$_DATE_ + message("Reading date from QMAKE ..." $$QDATE) + QDATE_SPLIT = $$split(QDATE) + QDAY = $$member(QDATE_SPLIT,2) + + !isEmpty(TEST1) { + contains( QDAY, $$DATE_Z ) { + message("Assuming YYYY/MM/DD format") + VERSION_YEAR = $$DATE_X + VERSION_MONTH = $$DATE_Y + VERSION_DAY = $$DATE_Z + } + } else { + !isEmpty(TEST2) { + contains( DATE_X, $$QDAY ) { + message("Assuming DD/MM/YYYY format" $$DATE_X $$DATE_Y $$DATE_Z ) + VERSION_DAY = $$DATE_X + VERSION_MONTH = $$DATE_Y + VERSION_YEAR = $$DATE_Z + } else { + message("Assuming MM/DD/YYYY format" $$DATE_X $$DATE_Y $$DATE_Z ) + VERSION_MONTH = $$DATE_X + VERSION_DAY = $$DATE_Y + VERSION_YEAR = $$DATE_Z + } + } else { + # test1 and test2 both empty + error("Couldn't parse Windows date. please run 'qmake VERSION=YYYY.MM.DD' with todays date") + } + } + message("YMD version:" $$VERSION_YEAR $$VERSION_MONTH $$VERSION_DAY) + } else { + # Unix/Mac + VERSION = $$system(date "+%Y.%m.%d") + VERSION_SPLIT=$$split(VERSION, ".") + VERSION_YEAR=$$member(VERSION_SPLIT, 0) + VERSION_MONTH=$$member(VERSION_SPLIT, 1) + VERSION_DAY=$$member(VERSION_SPLIT, 2) + } } -VERSION_SPLIT=$$split(VERSION, ".") -VERSION_YEAR=$$member(VERSION_SPLIT, 0) -VERSION_MONTH=$$member(VERSION_SPLIT, 1) -VERSION_DAY=$$member(VERSION_SPLIT, 2) #configure lex / yacc unix:freebsd-g++ { From cbde518369b906febe816b199fd8e9d4d75231c0 Mon Sep 17 00:00:00 2001 From: don bright Date: Sun, 23 Oct 2011 20:19:07 -0500 Subject: [PATCH 68/93] fix windows compile (glew.h / gl.h issue) --- tests/OffscreenContextWGL.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/OffscreenContextWGL.cc b/tests/OffscreenContextWGL.cc index 6e202a40..3b966e24 100644 --- a/tests/OffscreenContextWGL.cc +++ b/tests/OffscreenContextWGL.cc @@ -13,7 +13,6 @@ For more info: #include #include -#include #include "OffscreenContext.h" #include "printutils.h" @@ -21,6 +20,8 @@ For more info: #include "system-gl.h" #include "fbo.h" +#include // must be included after glew.h + using namespace std; struct OffscreenContext From 82ff5a12ec876c80a626357557fa8afdbf0721cc Mon Sep 17 00:00:00 2001 From: don bright Date: Sun, 23 Oct 2011 22:41:03 -0500 Subject: [PATCH 69/93] fdef getopt.h for MSVC, cleanup makefile, ignore another msvc warning --- openscad.pro | 25 +++++++++++++++---------- tests/CMakeLists.txt | 2 ++ tests/echotest.cc | 2 ++ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/openscad.pro b/openscad.pro index f5c5449d..40c7d3d5 100644 --- a/openscad.pro +++ b/openscad.pro @@ -8,11 +8,13 @@ } } -win32 { # for debugging link problems (use nmake -f Makefile.Release > log.txt) -# QMAKE_LFLAGS += -VERBOSE +win32 { + # QMAKE_LFLAGS += -VERBOSE } +# get VERSION from system date + isEmpty(VERSION) { win32 { # @@ -97,14 +99,17 @@ DEFINES += OPENSCAD_VERSION=$$VERSION OPENSCAD_YEAR=$$VERSION_YEAR OPENSCAD_MONT !isEmpty(VERSION_DAY): DEFINES += OPENSCAD_DAY=$$VERSION_DAY win32:DEFINES += _USE_MATH_DEFINES NOMINMAX _CRT_SECURE_NO_WARNINGS YY_NO_UNISTD_H -#disable warning about too long decorated names -win32:QMAKE_CXXFLAGS += -wd4503 - -#disable warning about casting int to bool -win32:QMAKE_CXXFLAGS += -wd4800 - -#disable warning about CGAL's unreferenced formal parameters -win32:QMAKE_CXXFLAGS += -wd4100 +# disable MSVC warnings that are of very low importance +win32:*msvc* { + # disable warning about too long decorated names + QMAKE_CXXFLAGS += -wd4503 + # CGAL casting int to bool + QMAKE_CXXFLAGS += -wd4800 + # CGAL's unreferenced formal parameters + QMAKE_CXXFLAGS += -wd4100 + # lexer uses strdup() & other POSIX stuff + QMAKE_CXXFLAGS += -D_CRT_NONSTDC_NO_DEPRECATE +} # disable Eigen SIMD optimizations for non-Mac OSX !macx { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 22d4f969..bc0d071c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -42,6 +42,8 @@ if(WIN32) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4100") # fopen_s advertisement set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_CRT_SECURE_NO_DEPRECATE") + # lexer uses strdup & other POSIX stuff + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_CRT_NONSTDC_NO_DEPRECATE") # M_PI set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_USE_MATH_DEFINES") endif() diff --git a/tests/echotest.cc b/tests/echotest.cc index c53e47fc..5b216f4d 100644 --- a/tests/echotest.cc +++ b/tests/echotest.cc @@ -37,7 +37,9 @@ #include #include #include +#ifndef _MSC_VER #include +#endif #include #include #include From ff6d6cda13040e69ef15bb8989ca0f436a8020cc Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Mon, 24 Oct 2011 13:46:46 +0200 Subject: [PATCH 70/93] Evaluate expressions for the ternary operator the same way as for if-else, i.e. cast any value to bool --- RELEASE_NOTES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index aaeddb11..3af3c1ae 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -8,7 +8,7 @@ o New import() statement reads the correct file format based on the filename ext (.stl, .dxf and .off is supported) o The color() statement now supports an alpha parameter, e.g. color(c=[1,0,0], alpha=0.4) o The color() statement now supports specifying colors as strings, e.g. color("Red") -o if() and else() can now take any value type as parameter. false, 0, empty string and empty vector or illegal value type will evaluate as false, everything else as true. +o if()/else() and the ternary operator can now take any value type as parameter. false, 0, empty string and empty vector or illegal value type will evaluate as false, everything else as true. o Strings can now be lexographically compared using the <, <=, >, >= operators o The version() function will return the OpenSCAD version as a vector, e.g. [2011, 09] o The version_num() function will return the OpenSCAD version as a number, e.g. 20110923 From 36895ff2e50e27206adde0a44dd8c265669eccce Mon Sep 17 00:00:00 2001 From: don bright Date: Mon, 24 Oct 2011 21:47:22 -0500 Subject: [PATCH 71/93] confine date detection to win32-msvc, set VERSION, hide debug message --- openscad.pro | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/openscad.pro b/openscad.pro index 40c7d3d5..099af35a 100644 --- a/openscad.pro +++ b/openscad.pro @@ -16,14 +16,13 @@ win32 { # get VERSION from system date isEmpty(VERSION) { - win32 { + win32-msvc*: { # # Windows XP date command only has one argument, /t # and it can print the date in various localized formats. # This code will detect MM/DD/YYYY, YYYY/MM/DD, and DD/MM/YYYY # SYSDATE = $$system(date /t) - message("Reading date from Windows date.exe... " $$SYSDATE) SYSDATE = $$replace(SYSDATE,"/",".") SYSDATE ~= s/[A-Za-z]*// # remove name of day DATE_SPLIT=$$split(SYSDATE, ".") @@ -34,13 +33,12 @@ isEmpty(VERSION) { TEST2=$$find(DATE_Z, [0-9]{4} ) QDATE = $$_DATE_ - message("Reading date from QMAKE ..." $$QDATE) QDATE_SPLIT = $$split(QDATE) QDAY = $$member(QDATE_SPLIT,2) !isEmpty(TEST1) { contains( QDAY, $$DATE_Z ) { - message("Assuming YYYY/MM/DD format") + # message("Assuming YYYY/MM/DD format") VERSION_YEAR = $$DATE_X VERSION_MONTH = $$DATE_Y VERSION_DAY = $$DATE_Z @@ -48,12 +46,12 @@ isEmpty(VERSION) { } else { !isEmpty(TEST2) { contains( DATE_X, $$QDAY ) { - message("Assuming DD/MM/YYYY format" $$DATE_X $$DATE_Y $$DATE_Z ) + # message("Assuming DD/MM/YYYY format" $$DATE_X $$DATE_Y $$DATE_Z ) VERSION_DAY = $$DATE_X VERSION_MONTH = $$DATE_Y VERSION_YEAR = $$DATE_Z } else { - message("Assuming MM/DD/YYYY format" $$DATE_X $$DATE_Y $$DATE_Z ) + # message("Assuming MM/DD/YYYY format" $$DATE_X $$DATE_Y $$DATE_Z ) VERSION_MONTH = $$DATE_X VERSION_DAY = $$DATE_Y VERSION_YEAR = $$DATE_Z @@ -62,8 +60,9 @@ isEmpty(VERSION) { # test1 and test2 both empty error("Couldn't parse Windows date. please run 'qmake VERSION=YYYY.MM.DD' with todays date") } - } - message("YMD version:" $$VERSION_YEAR $$VERSION_MONTH $$VERSION_DAY) + } # isEmpty(TEST1) + VERSION = $$VERSION_YEAR"."$$VERSION_MONTH"."$$VERSION_DAY + # message("YMD Version:" $$VERSION) } else { # Unix/Mac VERSION = $$system(date "+%Y.%m.%d") From 6b495672dbd0629beb5f1ec6cf2ca7cbaf087bb9 Mon Sep 17 00:00:00 2001 From: don bright Date: Mon, 24 Oct 2011 22:44:16 -0500 Subject: [PATCH 72/93] remove experimental html output debug code. --- tests/test_cmdline_tool.py | 52 -------------------------------------- 1 file changed, 52 deletions(-) diff --git a/tests/test_cmdline_tool.py b/tests/test_cmdline_tool.py index b6afbecb..42fca74e 100755 --- a/tests/test_cmdline_tool.py +++ b/tests/test_cmdline_tool.py @@ -69,56 +69,12 @@ def compare_default(resultfilename): return False return True -def append_html_output(expectedfilename, resultfilename): - # 1 if html directory & file not there, create them - # 2 copy expected filename image and result filename image to dir - # 3 append html to show differences - # 4 dump platform.platform() - expectedimg = os.path.basename(expectedfilename) - resultimg = os.path.basename(resultfilename) - template = ''' -

-

-

-''' - html = template - html = html.replace('///EXPECTED///',expectedimg) - html = html.replace('///RESULT///',resultimg) - html = html.replace('///TESTCMD///',os.path.basename(options.cmd)) - html = html.replace('///TESTNAME///',options.testname) - html = html.replace('///PLATFORM///',platform.platform()) - try: - shutil.copy(expectedfilename,options.imgdiff_dir) - shutil.copy(resultfilename,options.imgdiff_dir) - f = open(options.imgdiff_htmlfile,'a') - f.write(html) - f.close() - print >> sys.stderr, "appended " + options.imgdiff_htmlfile - except: - print >> sys.stderr, "error appending " + options.imgdiff_htmlfile - print >> sys.stderr, sys.exc_info() - def compare_png(resultfilename): if not resultfilename: print >> sys.stderr, "Error: OpenSCAD did not generate an image" return False print >> sys.stderr, 'Yee image compare: ', expectedfilename, ' ', resultfilename if execute_and_redirect("./yee_compare", [expectedfilename, resultfilename, "-downsample", "2", "-threshold", "200"], sys.stderr) != 0: - append_html_output(expectedfilename, resultfilename) return False return True @@ -188,14 +144,6 @@ if __name__ == '__main__': options.generate = False options.suffix = "txt" - options.imgdiff_dir = 'imgdiff-fail' - options.imgdiff_htmlfile = os.path.join(options.imgdiff_dir,'failed.html') - try: - if not os.path.isdir(options.imgdiff_dir): - os.mkdir(options.imgdiff_dir) - except: - print >> sys.stderr, "error creating " + options.imgdiff_dir, sys.exc_info() - for o, a in opts: if o in ("-g", "--generate"): options.generate = True elif o in ("-s", "--suffix"): From 5f58255bcf0602b40317d5236bad2e4473b5111b Mon Sep 17 00:00:00 2001 From: Don Bright Date: Tue, 25 Oct 2011 19:10:28 -0500 Subject: [PATCH 73/93] integrate Brad Pitcher's cross compile linux->win32-mingw qmake stuff --- boost.pri | 14 +++++++++++--- cgal.pri | 17 +++++++++++------ eigen2.pri | 17 ++++++++++------- glew.pri | 2 ++ openscad.pro | 15 +++++++++++++++ src/lexer.l | 7 +++++-- 6 files changed, 54 insertions(+), 18 deletions(-) diff --git a/boost.pri b/boost.pri index 34a9dd2e..078a17fc 100644 --- a/boost.pri +++ b/boost.pri @@ -1,4 +1,5 @@ boost { + isEmpty(DEPLOYDIR) { # Optionally specify location of boost using the # BOOSTDIR env. variable @@ -10,9 +11,16 @@ boost { } } - win32 { - LIBS += -llibboost_thread-vc90-mt-s-1_46_1 -llibboost_program_options-vc90-mt-s-1_46_1 + CONFIG(mingw-cross-env) { + DEFINES += BOOST_STATIC + DEFINES += BOOST_THREAD_USE_LIB + DEFINES += Boost_USE_STATIC_LIBS + LIBS += -lboost_thread_win32-mt -lboost_program_options-mt } else { - LIBS += -lboost_thread -lboost_program_options + win32 { + LIBS += -llibboost_thread-vc90-mt-s-1_46_1 -llibboost_program_options-vc90-mt-s-1_46_1 + } else { + LIBS += -lboost_thread -lboost_program_options + } } } diff --git a/cgal.pri b/cgal.pri index c0650449..1d9ef229 100644 --- a/cgal.pri +++ b/cgal.pri @@ -13,14 +13,19 @@ cgal { } } - windows { - *-g++* { - QMAKE_CXXFLAGS += -frounding-math - } - LIBS += $$CGAL_DIR/auxiliary/gmp/lib/libmpfr-4.lib -lCGAL-vc90-mt-s - } else { + CONFIG(mingw-cross-env) { LIBS += -lgmp -lmpfr -lCGAL QMAKE_CXXFLAGS += -frounding-math + } else { + windows { + *-g++* { + QMAKE_CXXFLAGS += -frounding-math + } + LIBS += $$CGAL_DIR/auxiliary/gmp/lib/libmpfr-4.lib -lCGAL-vc90-mt-s + } else { + LIBS += -lgmp -lmpfr -lCGAL + QMAKE_CXXFLAGS += -frounding-math + } } } diff --git a/eigen2.pri b/eigen2.pri index dac52f58..ce21e727 100644 --- a/eigen2.pri +++ b/eigen2.pri @@ -5,14 +5,17 @@ EIGEN2_DIR = $$(EIGEN2DIR) INCLUDEPATH += $$EIGEN2_DIR } else { - unix { - freebsd-g++ { - INCLUDEPATH += /usr/local/include/eigen2 + CONFIG(mingw-cross-env) { + INCLUDEPATH += mingw-cross-env/include/eigen2 + } else { + unix { + freebsd-g++ { + INCLUDEPATH += /usr/local/include/eigen2 + } else { + macx: INCLUDEPATH += /opt/local/include/eigen2 + } } else { - macx: INCLUDEPATH += /opt/local/include/eigen2 + INCLUDEPATH += /usr/include/eigen2 } } - else { - INCLUDEPATH += /usr/include/eigen2 - } } diff --git a/glew.pri b/glew.pri index f2aca4e4..84d94494 100644 --- a/glew.pri +++ b/glew.pri @@ -16,4 +16,6 @@ glew { unix:LIBS += -lGLEW win32:LIBS += -lglew32s + CONFIG(mingw-cross-env):DEFINES += GLEW_STATIC } + diff --git a/openscad.pro b/openscad.pro index 099af35a..fbe0fa7c 100644 --- a/openscad.pro +++ b/openscad.pro @@ -73,6 +73,21 @@ isEmpty(VERSION) { } } +# cross compilation unix->win32 + +CONFIG(mingw-cross-env) { + LIBS += mingw-cross-env/lib/libglew32s.a + LIBS += mingw-cross-env/lib/libglut.a + LIBS += mingw-cross-env/lib/libopengl32.a + LIBS += mingw-cross-env/lib/libGLEW.a + LIBS += mingw-cross-env/lib/libglaux.a + LIBS += mingw-cross-env/lib/libglu32.a + LIBS += mingw-cross-env/lib/libopencsg.a + LIBS += mingw-cross-env/lib/libmpfr.a + LIBS += mingw-cross-env/lib/libCGAL.a + QMAKE_CXXFLAGS += -fpermissive +} + #configure lex / yacc unix:freebsd-g++ { QMAKE_LEX = /usr/local/bin/flex diff --git a/src/lexer.l b/src/lexer.l index 3cd4a19c..cf5cb6d8 100644 --- a/src/lexer.l +++ b/src/lexer.l @@ -35,8 +35,11 @@ #include #include -//isatty for visual c++ -#ifdef _MSC_VER +//isatty for visual c++ and mingw-cross-env +#if defined __WIN32__ && ! defined _MSC_VER +#include "unistd.h" +#endif +#if defined __WIN32__ || defined _MSC_VER extern "C" int __cdecl _isatty(int _FileHandle); #define isatty _isatty #endif From 44ef8290c4d36be2d7eef4cd39523f205dd39258 Mon Sep 17 00:00:00 2001 From: don bright Date: Wed, 26 Oct 2011 02:20:01 -0400 Subject: [PATCH 74/93] fix broken linux build (eigen) --- eigen2.pri | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/eigen2.pri b/eigen2.pri index ce21e727..4c717492 100644 --- a/eigen2.pri +++ b/eigen2.pri @@ -8,14 +8,8 @@ else { CONFIG(mingw-cross-env) { INCLUDEPATH += mingw-cross-env/include/eigen2 } else { - unix { - freebsd-g++ { - INCLUDEPATH += /usr/local/include/eigen2 - } else { - macx: INCLUDEPATH += /opt/local/include/eigen2 - } - } else { - INCLUDEPATH += /usr/include/eigen2 - } + freebsd-g++: INCLUDEPATH += /usr/local/include/eigen2 + macx: INCLUDEPATH += /opt/local/include/eigen2 + !macx:!freebsd-g++:INCLUDEPATH += /usr/include/eigen2 } } From 0f7ea1ddc0d695726c9f66591cfedf50c5310561 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Wed, 26 Oct 2011 16:59:26 +0200 Subject: [PATCH 75/93] Minor namespace fixes --- src/Tree.h | 6 ++---- src/color.cc | 6 ++---- src/nodedumper.h | 12 ++++-------- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/Tree.h b/src/Tree.h index aaa61d70..41ae6138 100644 --- a/src/Tree.h +++ b/src/Tree.h @@ -3,8 +3,6 @@ #include "nodecache.h" -using std::string; - /*! For now, just an abstraction of the node tree which keeps a dump cache based on node indices around. @@ -20,8 +18,8 @@ public: void setRoot(const AbstractNode *root); const AbstractNode *root() const { return this->root_node; } - const string &getString(const AbstractNode &node) const; - const string &getIdString(const AbstractNode &node) const; + const std::string &getString(const AbstractNode &node) const; + const std::string &getIdString(const AbstractNode &node) const; private: const AbstractNode *root_node; diff --git a/src/color.cc b/src/color.cc index ee8f8722..3c6942c3 100644 --- a/src/color.cc +++ b/src/color.cc @@ -42,8 +42,6 @@ public: virtual AbstractNode *evaluate(const Context *ctx, const ModuleInstantiation *inst) const; }; -using std::string; - AbstractNode *ColorModule::evaluate(const Context *ctx, const ModuleInstantiation *inst) const { ColorNode *node = new ColorNode(inst); @@ -87,7 +85,7 @@ AbstractNode *ColorModule::evaluate(const Context *ctx, const ModuleInstantiatio return node; } -string ColorNode::toString() const +std::string ColorNode::toString() const { std::stringstream stream; @@ -96,7 +94,7 @@ string ColorNode::toString() const return stream.str(); } -string ColorNode::name() const +std::string ColorNode::name() const { return "color"; } diff --git a/src/nodedumper.h b/src/nodedumper.h index efaf4fa4..aca17edb 100644 --- a/src/nodedumper.h +++ b/src/nodedumper.h @@ -7,10 +7,6 @@ #include "visitor.h" #include "nodecache.h" -using std::string; -using std::map; -using std::list; - class NodeDumper : public Visitor { public: @@ -26,15 +22,15 @@ private: void handleVisitedChildren(const State &state, const AbstractNode &node); bool isCached(const AbstractNode &node) const; void handleIndent(const State &state); - string dumpChildren(const AbstractNode &node); + std::string dumpChildren(const AbstractNode &node); NodeCache &cache; bool idprefix; - string currindent; + std::string currindent; const AbstractNode *root; - typedef list ChildList; - map visitedchildren; + typedef std::list ChildList; + std::map visitedchildren; }; #endif From ed54572c9b6d31abc7dddd14d0acc7a153db11a4 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Wed, 26 Oct 2011 17:00:18 +0200 Subject: [PATCH 76/93] Make some code more readable and better error reporting on a reporter assert error --- src/CGALEvaluator.cc | 44 ++++++++++++++++++++++++++------------------ src/CGALEvaluator.h | 3 ++- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc index 10160ae9..18b92f92 100644 --- a/src/CGALEvaluator.cc +++ b/src/CGALEvaluator.cc @@ -27,6 +27,8 @@ #include #include +#include + CGAL_Nef_polyhedron CGALEvaluator::evaluateCGALMesh(const AbstractNode &node) { if (!isCached(node)) { @@ -75,22 +77,25 @@ void CGALEvaluator::process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedr CGAL_Nef_polyhedron CGALEvaluator::applyToChildren(const AbstractNode &node, CGALEvaluator::CsgOp op) { CGAL_Nef_polyhedron N; - if (this->visitedchildren[node.index()].size() > 0) { - for (ChildList::const_iterator iter = this->visitedchildren[node.index()].begin(); - iter != this->visitedchildren[node.index()].end(); - iter++) { - const AbstractNode *chnode = iter->first; - const string &chcacheid = iter->second; - // FIXME: Don't use deep access to modinst members - if (chnode->modinst->tag_background) continue; - assert(isCached(*chnode)); - if (N.empty()) { - N = CGALCache::instance()->get(chcacheid).copy(); - } else { - process(N, CGALCache::instance()->get(chcacheid), op); - } - chnode->progress_report(); + BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { + const AbstractNode *chnode = item.first; + const std::string &chcacheid = item.second; + // FIXME: Don't use deep access to modinst members + if (chnode->modinst->tag_background) continue; +// assert(isCached(*chnode)); + if (!isCached(*chnode)) { + PRINTF("Error - Not cached: Node %d", chnode->index()); + PRINTF(" chcacheid = %s", chcacheid.c_str()); + PRINTF(" getIdString() = %s", this->tree.getIdString(node).c_str()); + assert(false); } + + if (N.empty()) { + N = CGALCache::instance()->get(chcacheid).copy(); + } else { + process(N, CGALCache::instance()->get(chcacheid), op); + } + chnode->progress_report(); } return N; } @@ -107,7 +112,7 @@ CGAL_Nef_polyhedron CGALEvaluator::applyHull(const CgaladvNode &node) iter != this->visitedchildren[node.index()].end(); iter++) { const AbstractNode *chnode = iter->first; - const string &chcacheid = iter->second; + const std::string &chcacheid = iter->second; // FIXME: Don't use deep access to modinst members if (chnode->modinst->tag_background) continue; assert(isCached(*chnode)); @@ -520,9 +525,12 @@ CGAL_Nef_polyhedron CGALEvaluator::evaluateCGALMesh(const PolySet &ps) }; PolyReducer pr(ps); - PRINTF("Number of polygons before reduction: %d\n", pr.polygons.size()); + int numpolygons_before = pr.polygons.size(); pr.reduce(); - PRINTF("Number of polygons after reduction: %d\n", pr.polygons.size()); + int numpolygons_after = pr.polygons.size(); + if (numpolygons_after < numpolygons_before) { + PRINTF("reduce polygons: %d -> %d", numpolygons_before, numpolygons_after); + } return CGAL_Nef_polyhedron(pr.toNef()); #endif #if 0 diff --git a/src/CGALEvaluator.h b/src/CGALEvaluator.h index 0ac716ce..6a260435 100644 --- a/src/CGALEvaluator.h +++ b/src/CGALEvaluator.h @@ -37,7 +37,8 @@ private: CGAL_Nef_polyhedron applyHull(const CgaladvNode &node); std::string currindent; - typedef std::list > ChildList; + typedef std::pair ChildItem; + typedef std::list ChildList; std::map visitedchildren; const Tree &tree; From 3080932440266b1f67ba106a536f3e6e6305fa80 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 27 Oct 2011 00:49:55 +0200 Subject: [PATCH 77/93] Bugfix: Changed caching strategy to avoid the risk of sibling nodes being evicted from the cache before the parent node has evaluated. --- src/CGALEvaluator.cc | 124 +++++++++++++++++++++++-------------------- src/CGALEvaluator.h | 4 +- 2 files changed, 67 insertions(+), 61 deletions(-) diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc index 18b92f92..550f300c 100644 --- a/src/CGALEvaluator.cc +++ b/src/CGALEvaluator.cc @@ -79,22 +79,20 @@ CGAL_Nef_polyhedron CGALEvaluator::applyToChildren(const AbstractNode &node, CGA CGAL_Nef_polyhedron N; BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { const AbstractNode *chnode = item.first; - const std::string &chcacheid = item.second; + const CGAL_Nef_polyhedron &chN = item.second; // FIXME: Don't use deep access to modinst members if (chnode->modinst->tag_background) continue; -// assert(isCached(*chnode)); - if (!isCached(*chnode)) { - PRINTF("Error - Not cached: Node %d", chnode->index()); - PRINTF(" chcacheid = %s", chcacheid.c_str()); - PRINTF(" getIdString() = %s", this->tree.getIdString(node).c_str()); - assert(false); - } - if (N.empty()) { - N = CGALCache::instance()->get(chcacheid).copy(); - } else { - process(N, CGALCache::instance()->get(chcacheid), op); + // NB! We insert into the cache here to ensure that all children of + // a node is a valid object. If we inserted as we created them, the + // cache could have been modified before we reach this point due to a large + // sibling object. + if (!isCached(*chnode)) { + CGALCache::instance()->insert(this->tree.getIdString(*chnode), chN); } + if (N.empty()) N = chN.copy(); + else process(N, chN, op); + chnode->progress_report(); } return N; @@ -105,31 +103,25 @@ extern CGAL_Nef_polyhedron2 *convexhull2(std::list a); CGAL_Nef_polyhedron CGALEvaluator::applyHull(const CgaladvNode &node) { CGAL_Nef_polyhedron N; - if (this->visitedchildren[node.index()].size() > 0) { - std::list polys; - bool all2d = true; - for (ChildList::const_iterator iter = this->visitedchildren[node.index()].begin(); - iter != this->visitedchildren[node.index()].end(); - iter++) { - const AbstractNode *chnode = iter->first; - const std::string &chcacheid = iter->second; - // FIXME: Don't use deep access to modinst members - if (chnode->modinst->tag_background) continue; - assert(isCached(*chnode)); - const CGAL_Nef_polyhedron &ch = CGALCache::instance()->get(chcacheid); - if (ch.dim == 2) { - polys.push_back(ch.p2.get()); - } - else if (ch.dim == 3) { - PRINT("WARNING: hull() is not implemented yet for 3D objects!"); - all2d = false; - } - chnode->progress_report(); + std::list polys; + bool all2d = true; + BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { + const AbstractNode *chnode = item.first; + const CGAL_Nef_polyhedron &chN = item.second; + // FIXME: Don't use deep access to modinst members + if (chnode->modinst->tag_background) continue; + if (chN.dim == 2) { + polys.push_back(chN.p2.get()); } - - if (all2d) { - N = CGAL_Nef_polyhedron(convexhull2(polys)); + else if (chN.dim == 3) { + PRINT("WARNING: hull() is not implemented yet for 3D objects!"); + all2d = false; } + chnode->progress_report(); + } + + if (all2d) { + N = CGAL_Nef_polyhedron(convexhull2(polys)); } return N; } @@ -145,11 +137,10 @@ Response CGALEvaluator::visit(State &state, const AbstractNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { - if (!isCached(node)) { - CGAL_Nef_polyhedron N = applyToChildren(node, CGE_UNION); - CGALCache::instance()->insert(this->tree.getIdString(node), N); - } - addToParent(state, node); + CGAL_Nef_polyhedron N; + if (!isCached(node)) N = applyToChildren(node, CGE_UNION); + else N = CGALCache::instance()->get(this->tree.getIdString(node)); + addToParent(state, node, N); } return ContinueTraversal; } @@ -158,11 +149,10 @@ Response CGALEvaluator::visit(State &state, const AbstractIntersectionNode &node { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { - if (!isCached(node)) { - CGAL_Nef_polyhedron N = applyToChildren(node, CGE_INTERSECTION); - CGALCache::instance()->insert(this->tree.getIdString(node), N); - } - addToParent(state, node); + CGAL_Nef_polyhedron N; + if (!isCached(node)) N = applyToChildren(node, CGE_INTERSECTION); + else N = CGALCache::instance()->get(this->tree.getIdString(node)); + addToParent(state, node, N); } return ContinueTraversal; } @@ -171,6 +161,7 @@ Response CGALEvaluator::visit(State &state, const CsgNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { + CGAL_Nef_polyhedron N; if (!isCached(node)) { CGALEvaluator::CsgOp op; switch (node.type) { @@ -186,10 +177,12 @@ Response CGALEvaluator::visit(State &state, const CsgNode &node) default: assert(false); } - CGAL_Nef_polyhedron N = applyToChildren(node, op); - CGALCache::instance()->insert(this->tree.getIdString(node), N); + N = applyToChildren(node, op); } - addToParent(state, node); + else { + N = CGALCache::instance()->get(this->tree.getIdString(node)); + } + addToParent(state, node, N); } return ContinueTraversal; } @@ -198,9 +191,10 @@ Response CGALEvaluator::visit(State &state, const TransformNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { + CGAL_Nef_polyhedron N; if (!isCached(node)) { // First union all children - CGAL_Nef_polyhedron N = applyToChildren(node, CGE_UNION); + N = applyToChildren(node, CGE_UNION); // Then apply transform // If there is no geometry under the transform, N will be empty and of dim 0, @@ -236,9 +230,11 @@ Response CGALEvaluator::visit(State &state, const TransformNode &node) node.matrix(2,0), node.matrix(2,1), node.matrix(2,2), node.matrix(2,3), node.matrix(3,3)); N.p3->transform(t); } - CGALCache::instance()->insert(this->tree.getIdString(node), N); } - addToParent(state, node); + else { + N = CGALCache::instance()->get(this->tree.getIdString(node)); + } + addToParent(state, node, N); } return ContinueTraversal; } @@ -247,18 +243,20 @@ Response CGALEvaluator::visit(State &state, const AbstractPolyNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { + CGAL_Nef_polyhedron N; if (!isCached(node)) { // Apply polyset operation shared_ptr ps = this->psevaluator.getPolySet(node, false); - CGAL_Nef_polyhedron N; if (ps) { N = evaluateCGALMesh(*ps); // print_messages_pop(); node.progress_report(); } - CGALCache::instance()->insert(this->tree.getIdString(node), N); } - addToParent(state, node); + else { + N = CGALCache::instance()->get(this->tree.getIdString(node)); + } + addToParent(state, node, N); } return ContinueTraversal; } @@ -267,8 +265,8 @@ Response CGALEvaluator::visit(State &state, const CgaladvNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { + CGAL_Nef_polyhedron N; if (!isCached(node)) { - CGAL_Nef_polyhedron N; CGALEvaluator::CsgOp op; switch (node.type) { case MINKOWSKI: @@ -287,9 +285,11 @@ Response CGALEvaluator::visit(State &state, const CgaladvNode &node) N = applyHull(node); break; } - CGALCache::instance()->insert(this->tree.getIdString(node), N); } - addToParent(state, node); + else { + N = CGALCache::instance()->get(this->tree.getIdString(node)); + } + addToParent(state, node, N); } return ContinueTraversal; } @@ -298,12 +298,18 @@ Response CGALEvaluator::visit(State &state, const CgaladvNode &node) Adds ourself to out parent's list of traversed children. Call this for _every_ node which affects output during the postfix traversal. */ -void CGALEvaluator::addToParent(const State &state, const AbstractNode &node) +void CGALEvaluator::addToParent(const State &state, const AbstractNode &node, const CGAL_Nef_polyhedron &N) { assert(state.isPostfix()); this->visitedchildren.erase(node.index()); if (state.parent()) { - this->visitedchildren[state.parent()->index()].push_back(std::make_pair(&node, this->tree.getIdString(node))); + this->visitedchildren[state.parent()->index()].push_back(std::make_pair(&node, N)); + } + else { + // Root node, insert into cache + if (!isCached(node)) { + CGALCache::instance()->insert(this->tree.getIdString(node), N); + } } } diff --git a/src/CGALEvaluator.h b/src/CGALEvaluator.h index 6a260435..1dce4d90 100644 --- a/src/CGALEvaluator.h +++ b/src/CGALEvaluator.h @@ -30,14 +30,14 @@ public: const Tree &getTree() const { return this->tree; } private: - void addToParent(const State &state, const AbstractNode &node); + void addToParent(const State &state, const AbstractNode &node, const CGAL_Nef_polyhedron &N); bool isCached(const AbstractNode &node) const; void process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, CGALEvaluator::CsgOp op); CGAL_Nef_polyhedron applyToChildren(const AbstractNode &node, CGALEvaluator::CsgOp op); CGAL_Nef_polyhedron applyHull(const CgaladvNode &node); std::string currindent; - typedef std::pair ChildItem; + typedef std::pair ChildItem; typedef std::list ChildList; std::map visitedchildren; From 7c606d36b01ede3625a6b863cd1b7df0b551135a Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 27 Oct 2011 15:15:04 +0200 Subject: [PATCH 78/93] Improved quality of linear_extrude with twist by forcing all quads to be tessellated the same direction --- .../linear_extrude-tests-expected.png | Bin 12143 -> 12351 bytes .../linear_extrude-tests-expected.png | Bin 12466 -> 12785 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/regression/cgalpngtest/linear_extrude-tests-expected.png b/tests/regression/cgalpngtest/linear_extrude-tests-expected.png index 4ed436117c7710cbfe6b2a4bff0885aff4c4492d..14867436e59cce1200bd52a7635ed7d2b5ada462 100644 GIT binary patch literal 12351 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 literal 12143 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 Gi2q;WPz#p; diff --git a/tests/regression/opencsgtest/linear_extrude-tests-expected.png b/tests/regression/opencsgtest/linear_extrude-tests-expected.png index 579479e315738f4aa4556b6e05c685af1102e278..f31b22ac861b79baf2c290f10600ef47a13a1346 100644 GIT binary patch literal 12785 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 Uj=7A3|CIwU);HIC52MWg0UJ&eO8@`> literal 12466 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 From 6b8fd15ff79ae90682992ee045f92f28b136c34a Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 27 Oct 2011 15:23:55 +0200 Subject: [PATCH 79/93] Improved quality of linear_extrude with twist by forcing all quads to be tessellated the same direction --- src/PolySetCGALEvaluator.cc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc index 7f62d0a7..190d75a0 100644 --- a/src/PolySetCGALEvaluator.cc +++ b/src/PolySetCGALEvaluator.cc @@ -181,6 +181,7 @@ cant_project_non_simple_polyhedron: static void add_slice(PolySet *ps, const DxfData &dxf, DxfData::Path &path, double rot1, double rot2, double h1, double h2) { + bool splitfirst = sin(rot2 - rot1) >= 0.0; for (size_t j = 1; j < path.indices.size(); j++) { int k = j - 1; @@ -197,10 +198,7 @@ static void add_slice(PolySet *ps, const DxfData &dxf, DxfData::Path &path, doub double kx2 = dxf.points[path.indices[k]][0] * cos(rot2*M_PI/180) + dxf.points[path.indices[k]][1] * sin(rot2*M_PI/180); double ky2 = dxf.points[path.indices[k]][0] * -sin(rot2*M_PI/180) + dxf.points[path.indices[k]][1] * cos(rot2*M_PI/180); - double dia1_len_sq = (jy1-ky2)*(jy1-ky2) + (jx1-kx2)*(jx1-kx2); - double dia2_len_sq = (jy2-ky1)*(jy2-ky1) + (jx2-kx1)*(jx2-kx1); - - if (dia1_len_sq > dia2_len_sq) + if (splitfirst) { ps->append_poly(); if (path.is_inner) { From 5c590dc26b769e5732f1f1c4a4686fe157833b28 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 27 Oct 2011 15:27:38 +0200 Subject: [PATCH 80/93] Updated test case with recent linear_extrude improvement --- .../linear_extrude-tests-expected.png | Bin 8648 -> 12023 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/regression/throwntogethertest/linear_extrude-tests-expected.png b/tests/regression/throwntogethertest/linear_extrude-tests-expected.png index 59b61e76aa7811ab9928375f7dd2cf0570d33537..b672783117a0f64aebd4e2d46a06b1a10708408a 100644 GIT binary patch literal 12023 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 a6cVx!;4&CaJcoxH+5w9b)+TRZl=(mA1(mD- literal 8648 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 Date: Thu, 27 Oct 2011 15:31:12 +0200 Subject: [PATCH 81/93] Better GL error reporting --- tests/system-gl.cc | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/tests/system-gl.cc b/tests/system-gl.cc index fe9bdce7..f95a5ca4 100644 --- a/tests/system-gl.cc +++ b/tests/system-gl.cc @@ -32,22 +32,11 @@ void glew_dump() { << endl; }; -const char * gl_errors[] = { - "GL_INVALID_ENUM", // 0x0500 - "GL_INVALID_VALUE", // 0x0501 - "GL_INVALID_OPERATION", // 0x0502 - "GL_OUT_OF_MEMORY" // 0x0503 -}; - bool report_glerror(const char * function) { GLenum tGLErr = glGetError(); if (tGLErr != GL_NO_ERROR) { - if ( (tGLErr-0x500)<=3 && (tGLErr-0x500)>=0 ) - cerr << "OpenGL error 0x" << hex << tGLErr << " (" - << gl_errors[tGLErr-0x500] << ") after " << function << endl; - else - cerr << "OpenGL error 0x" << hex << tGLErr << " after " << function << endl; + cerr << "OpenGL error 0x" << hex << tGLErr << ": " << gluErrorString(tGLErr) << " after " << function << endl; return true; } return false; From 0c0b261e4041d9e5fc0b3aef86cc870d39914ab2 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 27 Oct 2011 16:18:37 +0200 Subject: [PATCH 82/93] Increased threshold to 300 to avoid minor details from failing a test --- tests/test_cmdline_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cmdline_tool.py b/tests/test_cmdline_tool.py index 42fca74e..688026ef 100755 --- a/tests/test_cmdline_tool.py +++ b/tests/test_cmdline_tool.py @@ -74,7 +74,7 @@ def compare_png(resultfilename): print >> sys.stderr, "Error: OpenSCAD did not generate an image" return False print >> sys.stderr, 'Yee image compare: ', expectedfilename, ' ', resultfilename - if execute_and_redirect("./yee_compare", [expectedfilename, resultfilename, "-downsample", "2", "-threshold", "200"], sys.stderr) != 0: + if execute_and_redirect("./yee_compare", [expectedfilename, resultfilename, "-downsample", "2", "-threshold", "300"], sys.stderr) != 0: return False return True From 696c061eeca02f3d6f16728cf6c1bc81ac2f9d73 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 30 Oct 2011 02:15:25 +0100 Subject: [PATCH 83/93] extracted version config to separate .pri file --- openscad.pro | 63 +++------------------------------------------------- version.pri | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 60 deletions(-) create mode 100644 version.pri diff --git a/openscad.pro b/openscad.pro index fbe0fa7c..c507ab9c 100644 --- a/openscad.pro +++ b/openscad.pro @@ -8,71 +8,14 @@ } } +# Populate VERSION, VERSION_YEAR, VERSION_MONTH, VERSION_DATE from system date +include(version.pri) + # for debugging link problems (use nmake -f Makefile.Release > log.txt) win32 { # QMAKE_LFLAGS += -VERBOSE } -# get VERSION from system date - -isEmpty(VERSION) { - win32-msvc*: { - # - # Windows XP date command only has one argument, /t - # and it can print the date in various localized formats. - # This code will detect MM/DD/YYYY, YYYY/MM/DD, and DD/MM/YYYY - # - SYSDATE = $$system(date /t) - SYSDATE = $$replace(SYSDATE,"/",".") - SYSDATE ~= s/[A-Za-z]*// # remove name of day - DATE_SPLIT=$$split(SYSDATE, ".") - DATE_X=$$member(DATE_SPLIT, 0) - DATE_Y=$$member(DATE_SPLIT, 1) - DATE_Z=$$member(DATE_SPLIT, 2) - TEST1=$$find(DATE_X, [0-9]{4} ) - TEST2=$$find(DATE_Z, [0-9]{4} ) - - QDATE = $$_DATE_ - QDATE_SPLIT = $$split(QDATE) - QDAY = $$member(QDATE_SPLIT,2) - - !isEmpty(TEST1) { - contains( QDAY, $$DATE_Z ) { - # message("Assuming YYYY/MM/DD format") - VERSION_YEAR = $$DATE_X - VERSION_MONTH = $$DATE_Y - VERSION_DAY = $$DATE_Z - } - } else { - !isEmpty(TEST2) { - contains( DATE_X, $$QDAY ) { - # message("Assuming DD/MM/YYYY format" $$DATE_X $$DATE_Y $$DATE_Z ) - VERSION_DAY = $$DATE_X - VERSION_MONTH = $$DATE_Y - VERSION_YEAR = $$DATE_Z - } else { - # message("Assuming MM/DD/YYYY format" $$DATE_X $$DATE_Y $$DATE_Z ) - VERSION_MONTH = $$DATE_X - VERSION_DAY = $$DATE_Y - VERSION_YEAR = $$DATE_Z - } - } else { - # test1 and test2 both empty - error("Couldn't parse Windows date. please run 'qmake VERSION=YYYY.MM.DD' with todays date") - } - } # isEmpty(TEST1) - VERSION = $$VERSION_YEAR"."$$VERSION_MONTH"."$$VERSION_DAY - # message("YMD Version:" $$VERSION) - } else { - # Unix/Mac - VERSION = $$system(date "+%Y.%m.%d") - VERSION_SPLIT=$$split(VERSION, ".") - VERSION_YEAR=$$member(VERSION_SPLIT, 0) - VERSION_MONTH=$$member(VERSION_SPLIT, 1) - VERSION_DAY=$$member(VERSION_SPLIT, 2) - } -} - # cross compilation unix->win32 CONFIG(mingw-cross-env) { diff --git a/version.pri b/version.pri new file mode 100644 index 00000000..5cce3d58 --- /dev/null +++ b/version.pri @@ -0,0 +1,59 @@ +# get VERSION from system date + +isEmpty(VERSION) { + win32-msvc*: { + # + # Windows XP date command only has one argument, /t + # and it can print the date in various localized formats. + # This code will detect MM/DD/YYYY, YYYY/MM/DD, and DD/MM/YYYY + # + SYSDATE = $$system(date /t) + SYSDATE = $$replace(SYSDATE,"/",".") + SYSDATE ~= s/[A-Za-z]*// # remove name of day + DATE_SPLIT=$$split(SYSDATE, ".") + DATE_X=$$member(DATE_SPLIT, 0) + DATE_Y=$$member(DATE_SPLIT, 1) + DATE_Z=$$member(DATE_SPLIT, 2) + TEST1=$$find(DATE_X, [0-9]{4} ) + TEST2=$$find(DATE_Z, [0-9]{4} ) + + QDATE = $$_DATE_ + QDATE_SPLIT = $$split(QDATE) + QDAY = $$member(QDATE_SPLIT,2) + + !isEmpty(TEST1) { + contains( QDAY, $$DATE_Z ) { + # message("Assuming YYYY/MM/DD format") + VERSION_YEAR = $$DATE_X + VERSION_MONTH = $$DATE_Y + VERSION_DAY = $$DATE_Z + } + } else { + !isEmpty(TEST2) { + contains( DATE_X, $$QDAY ) { + # message("Assuming DD/MM/YYYY format" $$DATE_X $$DATE_Y $$DATE_Z ) + VERSION_DAY = $$DATE_X + VERSION_MONTH = $$DATE_Y + VERSION_YEAR = $$DATE_Z + } else { + # message("Assuming MM/DD/YYYY format" $$DATE_X $$DATE_Y $$DATE_Z ) + VERSION_MONTH = $$DATE_X + VERSION_DAY = $$DATE_Y + VERSION_YEAR = $$DATE_Z + } + } else { + # test1 and test2 both empty + error("Couldn't parse Windows date. please run 'qmake VERSION=YYYY.MM.DD' with todays date") + } + } # isEmpty(TEST1) + VERSION = $$VERSION_YEAR"."$$VERSION_MONTH"."$$VERSION_DAY + # message("YMD Version:" $$VERSION) + } else { + # Unix/Mac + VERSION = $$system(date "+%Y.%m.%d") + VERSION_SPLIT=$$split(VERSION, ".") + VERSION_YEAR=$$member(VERSION_SPLIT, 0) + VERSION_MONTH=$$member(VERSION_SPLIT, 1) + VERSION_DAY=$$member(VERSION_SPLIT, 2) + } +} From a0c72f0577bc70935badb624b5c060bc0cdc1c35 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 30 Oct 2011 02:16:04 +0100 Subject: [PATCH 84/93] Killed unnecessary using statement --- src/mainwin.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/mainwin.cc b/src/mainwin.cc index 43a973d2..cc30be50 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -92,8 +92,6 @@ using namespace boost::lambda; #endif // ENABLE_CGAL -using std::cerr; - // Global application state unsigned int GuiLocker::gui_locked = 0; From 56979129695ca12ae86bc9b3ea988f7a720d4c8a Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 30 Oct 2011 02:16:27 +0100 Subject: [PATCH 85/93] Minor eigen include fix --- src/dxfdata.h | 4 +--- src/linalg.h | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dxfdata.h b/src/dxfdata.h index d138e7a8..7eea6a9a 100644 --- a/src/dxfdata.h +++ b/src/dxfdata.h @@ -1,11 +1,9 @@ #ifndef DXFDATA_H_ #define DXFDATA_H_ -#include +#include "linalg.h" #include -using Eigen::Vector2d; - class DxfData { public: diff --git a/src/linalg.h b/src/linalg.h index 06991cf5..e20d8d88 100644 --- a/src/linalg.h +++ b/src/linalg.h @@ -3,7 +3,9 @@ #include #include +#include +using Eigen::Vector2d; using Eigen::Vector3d; typedef Eigen::AlignedBox BoundingBox; using Eigen::Matrix3f; From 5070014b3a42ccb0e81ca36689e17351b5faaf13 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 30 Oct 2011 02:25:57 +0100 Subject: [PATCH 86/93] Enable standard shortcuts for save and reload on Linux and Windows. F2/F3 will still work. --- src/mainwin.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/mainwin.cc b/src/mainwin.cc index be82d263..f9c3ac6b 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -218,8 +218,12 @@ MainWindow::MainWindow(const QString &filename) connect(this->fileActionReload, SIGNAL(triggered()), this, SLOT(actionReload())); connect(this->fileActionQuit, SIGNAL(triggered()), this, SLOT(quit())); #ifndef __APPLE__ - this->fileActionSave->setShortcut(QKeySequence(Qt::Key_F2)); - this->fileActionReload->setShortcut(QKeySequence(Qt::Key_F3)); + QList shortcuts = this->fileActionSave->shortcuts(); + shortcuts.push_back(QKeySequence(Qt::Key_F2)); + this->fileActionSave->setShortcuts(shortcuts); + shortcuts = this->fileActionReload->shortcuts(); + shortcuts.push_back(QKeySequence(Qt::Key_F3)); + this->fileActionReload->setShortcuts(shortcuts); #endif // Open Recent for (int i = 0;i Date: Sun, 30 Oct 2011 02:27:29 +0100 Subject: [PATCH 87/93] deprecation --- RELEASE_NOTES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 3af3c1ae..3e10eecf 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -13,6 +13,7 @@ o Strings can now be lexographically compared using the <, <=, >, >= operators o The version() function will return the OpenSCAD version as a vector, e.g. [2011, 09] o The version_num() function will return the OpenSCAD version as a number, e.g. 20110923 o Added PI constant. +o Now uses standard shortcuts for save and reload on Linux and Windows. F2/F3 will still work but is deprecated. Bugfixes: o square() crashed if any of the dimensions were zero @@ -28,6 +29,7 @@ o The file, layer, origin and scale parameters to linear_extrude() and rotate_ex are now deprecated. Use an import() child instead. o import_dxf(), import_stl() and import_off() are now deprecated. Use import() instead. o When exporting geometry from the cmd-line, use the universal -o option. It will export to the correct file format based on the given suffix (dxf, stl, off). +o F2 and F3 for Save and Reload is now deprecated OpenSCAD 2011.06 ================ From 741239402c760e06b77715347372be869e58f6ca Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 30 Oct 2011 13:10:51 +0100 Subject: [PATCH 88/93] import_dxf -> import --- testdata/scad/features/import_dxf-tests.scad | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/testdata/scad/features/import_dxf-tests.scad b/testdata/scad/features/import_dxf-tests.scad index 814a46c0..736f26ed 100644 --- a/testdata/scad/features/import_dxf-tests.scad +++ b/testdata/scad/features/import_dxf-tests.scad @@ -1,9 +1,9 @@ -import_dxf(); -translate([-210,0,0]) import_dxf(file="../../dxf/polygons.dxf"); -translate([-210,0,0]) import_dxf(file="../../dxf/polygons.dxf", origin=[0,110]); -translate([-210,0,0]) import_dxf(file="../../dxf/polygons.dxf", origin=[110,110], scale=0.5); -import_dxf(file="../../dxf/multiple-layers.dxf"); -translate([-200,200,0]) import_dxf(file="../../dxf/multiple-layers.dxf", layer="0"); -translate([0,200,0]) import_dxf(file="../../dxf/multiple-layers.dxf", layer="0"); -translate([200,200,0]) import_dxf(file="../../dxf/multiple-layers.dxf", layer="noname"); -translate([0,200,0]) import_dxf(file="../../dxf/multiple-layers.dxf", layer="Layer with a pretty long name including \\ \"special\" /'\\\\ characters"); +import(); +translate([-210,0,0]) import(file="../../dxf/polygons.dxf"); +translate([-210,0,0]) import(file="../../dxf/polygons.dxf", origin=[0,110]); +translate([-210,0,0]) import(file="../../dxf/polygons.dxf", origin=[110,110], scale=0.5); +import(file="../../dxf/multiple-layers.dxf"); +translate([-200,200,0]) import(file="../../dxf/multiple-layers.dxf", layer="0"); +translate([0,200,0]) import(file="../../dxf/multiple-layers.dxf", layer="0"); +translate([200,200,0]) import(file="../../dxf/multiple-layers.dxf", layer="noname"); +translate([0,200,0]) import(file="../../dxf/multiple-layers.dxf", layer="Layer with a pretty long name including \\ \"special\" /'\\\\ characters"); From a8ee02ebf518a1e4fde8fd703f1fb9feed189058 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 30 Oct 2011 13:49:28 +0100 Subject: [PATCH 89/93] Added test for rotation of open polyline --- testdata/dxf/open-polyline.dxf | 1904 +++++++++++++++++ .../features/rotate_extrude_dxf-tests.scad | 4 + tests/CMakeLists.txt | 3 +- .../rotate_extrude_dxf-tests-expected.png | Bin 0 -> 7535 bytes .../rotate_extrude_dxf-tests-expected.png | Bin 0 -> 7635 bytes .../rotate_extrude_dxf-tests-expected.png | Bin 0 -> 7635 bytes 6 files changed, 1910 insertions(+), 1 deletion(-) create mode 100644 testdata/dxf/open-polyline.dxf create mode 100644 testdata/scad/features/rotate_extrude_dxf-tests.scad create mode 100644 tests/regression/cgalpngtest/rotate_extrude_dxf-tests-expected.png create mode 100644 tests/regression/opencsgtest/rotate_extrude_dxf-tests-expected.png create mode 100644 tests/regression/throwntogethertest/rotate_extrude_dxf-tests-expected.png diff --git a/testdata/dxf/open-polyline.dxf b/testdata/dxf/open-polyline.dxf new file mode 100644 index 00000000..e8f3b058 --- /dev/null +++ b/testdata/dxf/open-polyline.dxf @@ -0,0 +1,1904 @@ +999 +dxflib 2.2.0.0 + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1015 + 9 +$HANDSEED + 5 +FFFF + 9 +$DIMASZ + 40 +2.5 + 9 +$PLIMMIN + 10 +0.0 + 20 +0.0 + 9 +$DIMEXE + 40 +1.25 + 9 +$DIMGAP + 40 +0.625 + 9 +$PLIMMAX + 10 +210.0 + 20 +297.0 + 9 +$INSUNITS + 70 +4 + 9 +$DIMEXO + 40 +0.625 + 9 +$DIMTXT + 40 +2.5 + 9 +$CLAYER + 8 +0 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +100 +AcDbSymbolTable + 70 +1 + 0 +VPORT + 5 +30 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*Active + 70 +0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +286.3055555555554861 + 22 +148.5 + 13 +0.0 + 23 +0.0 + 14 +10.0 + 24 +10.0 + 15 +10.0 + 25 +10.0 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +0.0 + 27 +0.0 + 37 +0.0 + 40 +297.0 + 41 +1.92798353909465 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 +0 + 72 +100 + 73 +1 + 74 +3 + 75 +1 + 76 +1 + 77 +0 + 78 +0 +281 +0 + 65 +1 +110 +0.0 +120 +0.0 +130 +0.0 +111 +1.0 +121 +0.0 +131 +0.0 +112 +0.0 +122 +1.0 +132 +0.0 + 79 +0 +146 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +5 +100 +AcDbSymbolTable + 70 +21 + 0 +LTYPE + 5 +14 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByBlock + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +LTYPE + 5 +15 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByLayer + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +LTYPE + 5 +16 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +CONTINUOUS + 70 +0 + 3 +Solid line + 72 +65 + 73 +0 + 40 +0.0 + 0 +LTYPE + 5 +31 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DOT + 70 +0 + 3 +Dot . . . . . . . . . . . . . . . . . . . . . . + 72 +65 + 73 +2 + 40 +6.3499999999999996 + 49 +0.0 + 74 +0 + 49 +-6.3499999999999996 + 74 +0 + 0 +LTYPE + 5 +32 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DOT2 + 70 +0 + 3 +Dot (.5x) ..................................... + 72 +65 + 73 +2 + 40 +3.1749999999999998 + 49 +0.0 + 74 +0 + 49 +-3.1749999999999998 + 74 +0 + 0 +LTYPE + 5 +33 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DOTX2 + 70 +0 + 3 +Dot (2x) . . . . . . . . . . . . . + 72 +65 + 73 +2 + 40 +12.6999999999999993 + 49 +0.0 + 74 +0 + 49 +-12.6999999999999993 + 74 +0 + 0 +LTYPE + 5 +34 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DASHED + 70 +0 + 3 +Dashed __ __ __ __ __ __ __ __ __ __ __ __ __ _ + 72 +65 + 73 +2 + 40 +19.0500000000000007 + 49 +12.6999999999999993 + 74 +0 + 49 +-6.3499999999999996 + 74 +0 + 0 +LTYPE + 5 +35 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DASHED2 + 70 +0 + 3 +Dashed (.5x) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + 72 +65 + 73 +2 + 40 +9.5250000000000004 + 49 +6.3499999999999996 + 74 +0 + 49 +-3.1749999999999998 + 74 +0 + 0 +LTYPE + 5 +36 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DASHEDX2 + 70 +0 + 3 +Dashed (2x) ____ ____ ____ ____ ____ ___ + 72 +65 + 73 +2 + 40 +38.1000000000000014 + 49 +25.3999999999999986 + 74 +0 + 49 +-12.6999999999999993 + 74 +0 + 0 +LTYPE + 5 +37 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DASHDOT + 70 +0 + 3 +Dash dot __ . __ . __ . __ . __ . __ . __ . __ + 72 +65 + 73 +4 + 40 +25.3999999999999986 + 49 +12.6999999999999993 + 74 +0 + 49 +-6.3499999999999996 + 74 +0 + 49 +0.0 + 74 +0 + 49 +-6.3499999999999996 + 74 +0 + 0 +LTYPE + 5 +38 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DASHDOT2 + 70 +0 + 3 +Dash dot (.5x) _._._._._._._._._._._._._._._. + 72 +65 + 73 +4 + 40 +12.6999999999999993 + 49 +6.3499999999999996 + 74 +0 + 49 +-3.1749999999999998 + 74 +0 + 49 +0.0 + 74 +0 + 49 +-3.1749999999999998 + 74 +0 + 0 +LTYPE + 5 +39 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DASHDOTX2 + 70 +0 + 3 +Dash dot (2x) ____ . ____ . ____ . ___ + 72 +65 + 73 +4 + 40 +50.7999999999999972 + 49 +25.3999999999999986 + 74 +0 + 49 +-12.6999999999999993 + 74 +0 + 49 +0.0 + 74 +0 + 49 +-12.6999999999999993 + 74 +0 + 0 +LTYPE + 5 +3A +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DIVIDE + 70 +0 + 3 +Divide ____ . . ____ . . ____ . . ____ . . ____ + 72 +65 + 73 +6 + 40 +31.75 + 49 +12.6999999999999993 + 74 +0 + 49 +-6.3499999999999996 + 74 +0 + 49 +0.0 + 74 +0 + 49 +-6.3499999999999996 + 74 +0 + 49 +0.0 + 74 +0 + 49 +-6.3499999999999996 + 74 +0 + 0 +LTYPE + 5 +3B +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DIVIDE2 + 70 +0 + 3 +Divide (.5x) __..__..__..__..__..__..__..__.._ + 72 +65 + 73 +6 + 40 +15.875 + 49 +6.3499999999999996 + 74 +0 + 49 +-3.1749999999999998 + 74 +0 + 49 +0.0 + 74 +0 + 49 +-3.1749999999999998 + 74 +0 + 49 +0.0 + 74 +0 + 49 +-3.1749999999999998 + 74 +0 + 0 +LTYPE + 5 +3C +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DIVIDEX2 + 70 +0 + 3 +Divide (2x) ________ . . ________ . . _ + 72 +65 + 73 +6 + 40 +63.5 + 49 +25.3999999999999986 + 74 +0 + 49 +-12.6999999999999993 + 74 +0 + 49 +0.0 + 74 +0 + 49 +-12.6999999999999993 + 74 +0 + 49 +0.0 + 74 +0 + 49 +-12.6999999999999993 + 74 +0 + 0 +LTYPE + 5 +3D +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +CENTER + 70 +0 + 3 +Center ____ _ ____ _ ____ _ ____ _ ____ _ ____ + 72 +65 + 73 +4 + 40 +50.7999999999999972 + 49 +31.75 + 74 +0 + 49 +-6.3499999999999996 + 74 +0 + 49 +6.3499999999999996 + 74 +0 + 49 +-6.3499999999999996 + 74 +0 + 0 +LTYPE + 5 +3E +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +CENTER2 + 70 +0 + 3 +Center (.5x) ___ _ ___ _ ___ _ ___ _ ___ _ ___ + 72 +65 + 73 +4 + 40 +28.5749999999999993 + 49 +19.0500000000000007 + 74 +0 + 49 +-3.1749999999999998 + 74 +0 + 49 +3.1749999999999998 + 74 +0 + 49 +-3.1749999999999998 + 74 +0 + 0 +LTYPE + 5 +3F +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +CENTERX2 + 70 +0 + 3 +Center (2x) ________ __ ________ __ _____ + 72 +65 + 73 +4 + 40 +101.5999999999999943 + 49 +63.5 + 74 +0 + 49 +-12.6999999999999993 + 74 +0 + 49 +12.6999999999999993 + 74 +0 + 49 +-12.6999999999999993 + 74 +0 + 0 +LTYPE + 5 +40 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BORDER + 70 +0 + 3 +Border __ __ . __ __ . __ __ . __ __ . __ __ . + 72 +65 + 73 +6 + 40 +44.4500000000000028 + 49 +12.6999999999999993 + 74 +0 + 49 +-6.3499999999999996 + 74 +0 + 49 +12.6999999999999993 + 74 +0 + 49 +-6.3499999999999996 + 74 +0 + 49 +0.0 + 74 +0 + 49 +-6.3499999999999996 + 74 +0 + 0 +LTYPE + 5 +41 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BORDER2 + 70 +0 + 3 +Border (.5x) __.__.__.__.__.__.__.__.__.__.__. + 72 +65 + 73 +6 + 40 +22.2250000000000014 + 49 +6.3499999999999996 + 74 +0 + 49 +-3.1749999999999998 + 74 +0 + 49 +6.3499999999999996 + 74 +0 + 49 +-3.1749999999999998 + 74 +0 + 49 +0.0 + 74 +0 + 49 +-3.1749999999999998 + 74 +0 + 0 +LTYPE + 5 +42 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BORDERX2 + 70 +0 + 3 +Border (2x) ____ ____ . ____ ____ . ___ + 72 +65 + 73 +6 + 40 +88.9000000000000057 + 49 +25.3999999999999986 + 74 +0 + 49 +-12.6999999999999993 + 74 +0 + 49 +25.3999999999999986 + 74 +0 + 49 +-12.6999999999999993 + 74 +0 + 49 +0.0 + 74 +0 + 49 +-12.6999999999999993 + 74 +0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 5 +2 +100 +AcDbSymbolTable + 70 +1 + 0 +LAYER + 5 +10 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +0 + 70 +0 + 62 +7 +420 +0 + 6 +CONTINUOUS +370 +25 +390 +F + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +3 +100 +AcDbSymbolTable + 70 +1 + 0 +STYLE + 5 +11 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +Standard + 70 +0 + 40 +0.0 + 41 +0.75 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +6 +100 +AcDbSymbolTable + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +7 +100 +AcDbSymbolTable + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +9 +100 +AcDbSymbolTable + 70 +1 + 0 +APPID + 5 +12 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +A +100 +AcDbSymbolTable + 70 +1 +100 +AcDbDimStyleTable + 71 +0 + 0 +DIMSTYLE +105 +27 +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +Standard + 41 +2.5 + 42 +0.625 + 43 +3.75 + 44 +1.25 + 70 +0 + 73 +0 + 74 +0 + 77 +1 + 78 +8 +140 +2.5 +141 +2.5 +143 +0.03937007874016 +147 +0.625 +171 +3 +172 +1 +271 +2 +272 +2 +274 +3 +278 +44 +283 +0 +284 +8 +340 +11 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +1 +100 +AcDbSymbolTable + 70 +1 + 0 +BLOCK_RECORD + 5 +1F +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Model_Space +340 +22 + 0 +BLOCK_RECORD + 5 +1B +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Paper_Space +340 +1E + 0 +BLOCK_RECORD + 5 +23 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Paper_Space0 +340 +26 + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +20 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Model_Space + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Model_Space + 1 + + 0 +ENDBLK + 5 +21 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +100 +AcDbEntity + 67 +1 + 8 +0 +100 +AcDbBlockBegin + 2 +*Paper_Space + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Paper_Space + 1 + + 0 +ENDBLK + 5 +1D +100 +AcDbEntity + 67 +1 + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +24 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Paper_Space0 + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Paper_Space0 + 1 + + 0 +ENDBLK + 5 +25 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +LINE + 5 +43 +100 +AcDbEntity +100 +AcDbLine + 8 +0 + 62 +256 +370 +-1 + 6 +ByLayer + 10 +100.0 + 20 +100.0 + 30 +0.0 + 11 +200.0 + 21 +100.0 + 31 +0.0 + 0 +LINE + 5 +44 +100 +AcDbEntity +100 +AcDbLine + 8 +0 + 62 +256 +370 +-1 + 6 +ByLayer + 10 +200.0 + 20 +100.0 + 30 +0.0 + 11 +200.0 + 21 +0.0 + 31 +0.0 + 0 +LINE + 5 +45 +100 +AcDbEntity +100 +AcDbLine + 8 +0 + 62 +256 +370 +-1 + 6 +ByLayer + 10 +200.0 + 20 +0.0 + 30 +0.0 + 11 +100.0 + 21 +0.0 + 31 +0.0 + 0 +LINE + 5 +46 +100 +AcDbEntity +100 +AcDbLine + 8 +0 + 62 +256 +370 +-1 + 6 +ByLayer + 10 +100.0 + 20 +0.0 + 30 +0.0 + 11 +100.0 + 21 +0.0 + 31 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +C +100 +AcDbDictionary +280 +0 +281 +1 + 3 +ACAD_GROUP +350 +D + 3 +ACAD_LAYOUT +350 +1A + 3 +ACAD_MLINESTYLE +350 +17 + 3 +ACAD_PLOTSETTINGS +350 +19 + 3 +ACAD_PLOTSTYLENAME +350 +E + 3 +AcDbVariableDictionary +350 +47 + 0 +DICTIONARY + 5 +D +100 +AcDbDictionary +280 +0 +281 +1 + 0 +ACDBDICTIONARYWDFLT + 5 +E +100 +AcDbDictionary +281 +1 + 3 +Normal +350 +F +100 +AcDbDictionaryWithDefault +340 +F + 0 +ACDBPLACEHOLDER + 5 +F + 0 +DICTIONARY + 5 +17 +100 +AcDbDictionary +280 +0 +281 +1 + 3 +Standard +350 +18 + 0 +MLINESTYLE + 5 +18 +100 +AcDbMlineStyle + 2 +STANDARD + 70 +0 + 3 + + 62 +256 + 51 +90.0 + 52 +90.0 + 71 +2 + 49 +0.5 + 62 +256 + 6 +BYLAYER + 49 +-0.5 + 62 +256 + 6 +BYLAYER + 0 +DICTIONARY + 5 +19 +100 +AcDbDictionary +280 +0 +281 +1 + 0 +DICTIONARY + 5 +1A +100 +AcDbDictionary +281 +1 + 3 +Layout1 +350 +1E + 3 +Layout2 +350 +26 + 3 +Model +350 +22 + 0 +LAYOUT + 5 +1E +100 +AcDbPlotSettings + 1 + + 2 +C:\Program Files\AutoCAD 2002\plotters\DWF ePlot (optimized for plotting).pc3 + 4 + + 6 + + 40 +0.0 + 41 +0.0 + 42 +0.0 + 43 +0.0 + 44 +0.0 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 +688 + 72 +0 + 73 +0 + 74 +5 + 7 + + 75 +16 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Layout1 + 70 +1 + 71 +1 + 10 +0.0 + 20 +0.0 + 11 +420.0 + 21 +297.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +100000000000000000000.0 + 24 +100000000000000000000.0 + 34 +100000000000000000000.0 + 15 +-100000000000000000000.0 + 25 +-100000000000000000000.0 + 35 +-100000000000000000000.0 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 +0 +330 +1B + 0 +LAYOUT + 5 +22 +100 +AcDbPlotSettings + 1 + + 2 +C:\Program Files\AutoCAD 2002\plotters\DWF ePlot (optimized for plotting).pc3 + 4 + + 6 + + 40 +0.0 + 41 +0.0 + 42 +0.0 + 43 +0.0 + 44 +0.0 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 +1712 + 72 +0 + 73 +0 + 74 +0 + 7 + + 75 +0 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Model + 70 +1 + 71 +0 + 10 +0.0 + 20 +0.0 + 11 +12.0 + 21 +9.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +0.0 + 24 +0.0 + 34 +0.0 + 15 +0.0 + 25 +0.0 + 35 +0.0 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 +0 +330 +1F + 0 +LAYOUT + 5 +26 +100 +AcDbPlotSettings + 1 + + 2 +C:\Program Files\AutoCAD 2002\plotters\DWF ePlot (optimized for plotting).pc3 + 4 + + 6 + + 40 +0.0 + 41 +0.0 + 42 +0.0 + 43 +0.0 + 44 +0.0 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 +688 + 72 +0 + 73 +0 + 74 +5 + 7 + + 75 +16 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Layout2 + 70 +1 + 71 +2 + 10 +0.0 + 20 +0.0 + 11 +12.0 + 21 +9.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +0.0 + 24 +0.0 + 34 +0.0 + 15 +0.0 + 25 +0.0 + 35 +0.0 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 +0 +330 +23 + 0 +DICTIONARY + 5 +47 +100 +AcDbDictionary +281 +1 + 3 +DIMASSOC +350 +49 + 3 +HIDETEXT +350 +48 + 0 +DICTIONARYVAR + 5 +48 +100 +DictionaryVariables +280 +0 + 1 +2 + 0 +DICTIONARYVAR + 5 +49 +100 +DictionaryVariables +280 +0 + 1 +1 + 0 +ENDSEC + 0 +EOF diff --git a/testdata/scad/features/rotate_extrude_dxf-tests.scad b/testdata/scad/features/rotate_extrude_dxf-tests.scad new file mode 100644 index 00000000..aacde690 --- /dev/null +++ b/testdata/scad/features/rotate_extrude_dxf-tests.scad @@ -0,0 +1,4 @@ +// These are tests which are not yet possible to express with the +// non-deprecated functionality + +rotate_extrude(file = "../../dxf/open-polyline.dxf"); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bc0d071c..6088ebc0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -373,7 +373,8 @@ LIST(APPEND CGALPNGTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/features/assign-tests.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/features/include-tests.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/features/child-tests.scad - ${CMAKE_SOURCE_DIR}/../testdata/scad/features/ifelse-tests.scad) + ${CMAKE_SOURCE_DIR}/../testdata/scad/features/ifelse-tests.scad + ${CMAKE_SOURCE_DIR}/../testdata/scad/features/rotate_extrude_dxf-tests.scad) LIST(APPEND CGALPNGTEST_FILES ${SCAD_DXF_FILES}) #LIST(APPEND CGALPNGTEST_FILES ${CMAKE_SOURCE_DIR}/../examples/example001.scad) add_cmdline_test(cgalpngtest png ${CGALPNGTEST_FILES}) diff --git a/tests/regression/cgalpngtest/rotate_extrude_dxf-tests-expected.png b/tests/regression/cgalpngtest/rotate_extrude_dxf-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..894362f844801e5a476af9a888a81cfe52f320e7 GIT binary patch literal 7535 zcmeHMXIE2Q(>^&Q^b(qMh=QPkASfLr*bpfqARwZI2NjiiO9vqVRJ^6BNRbv4Q2{AR zlbX-=kNWh%fc%?kj4fZ2t! zHUI!Zk08MP=O;}1Oc4Oc>6o20u?vP#CoG>RQ9`)ZCX&TG6wioRH;Gt0C_UX2C31i5 zs01R8XEsF^)JNR^dbmkVAAbMSezjOX9m(m8@KSG>8-M41T?$62nVdLi(#HK<`$FW| zl)66e(4gDyVfW~{1&pH5>;j4kJbk4&)HtHYa1~o%Y8y`b*&ODucLoQ*KpMPwFyph7 zC<=`uD#BnGIV1|5^8p7OatiKOM&ocmDJM4)Wo?HB#E#b8h1}49BL|!tdFTiVc)D-0 zoCk?Q0Uu!?oLfvB37q5e8HJ2U;0Lrc{3$Ot?#&}zFUZIZc>LMz90HF1-jtyO8R0l> zG!BS-1A;ksep+TgMvy2CMS;EygISlay|aY;VHknG;rvDCZ|(d&S$}!w|351m#o~y+ z;NI+JRi5?-%Xn;>uifLbm>A+ooMjSUCkpUZDCM%$+i|WyqH7Y zhkZGetST~n0sVeQ7zu>GlxCeac-@!rR~w%SIEau6$sk6a_IY*ZV?V{EdQyMG56`rt zyowhx_M54W?{SIP_%<|w-uZlSN6M^M-0Zg_@K)_>lF7&p{z4{m=h3pef~)`L+>J5% zQu#d0m)h!Xkh^457fzY@cDz-AH@ch;4ct_V4`)BfowCm+!F+Y*DH;#PC#f2QGB5RW z*%BN)d`E%%;(7Bw*gwVyP8!V$tj5h{A>D|RwZIpn*!1TpPW6(9M0YqE#p*9Eq%yMP z*oRwYN}j^e?mzN2(ER=d3N&lrY$u*}&3&1@9mI0irWiMjFb`7azyW?-K#YMais#4j zDdrG}3EeD{kM(4}XRxY21A6ZlXsujA-q?WO1e<*v_4}H+xp;8G);rqOyG4Yu#gZ^? zTtGb21-nMxOKVk$l+-Js-E`r`b$;kEaN1tMZuBQwtweljwgY| zmaHS)g-(7CiALxu`q zv6fxd<6LClP1-yeA4yU)^7JfR%|T#AO;39F{z&R@b>mah=CJoRXR@Tj%sf3@0M8`-*^t6bq&6iE zh2l7mzK%%(bFN;NsADkJ2_r|oBO@)KAlY#Ax1$y|%=_iE*#PWzJ-y6lUA?ZhTF;hAqrjE&tK5aNX|>mXQCi z%xPEANPN8?wmz#>rOKzCc3)w;0?;FMUu^_cf+nyhzQAi-pz+fYEo_Q!n#QKcUiaM) zyun_{6(T$D8LNW{YM%f-kMq_dZ4XDTK zn~!fC5S^P}4Zlb8>4}!paU@4`)n2;l#Y_%jPw3sCDw26~5?{Cl@?_p7c3xuM`k|ip zxMYI0R4Z^TBfz#Ke6IUyqVa<@kUT8%Q61b9t2}K)5PJr1SI?(X#^_aJ!E;C$%=byU zUm$lTL1T-$`f}H8Ku)aa#^=?l7XwzNXuuyW7_x8xlRN6d8X4hwX&x1SDhyw~x~hD? zsCw*uPa*hc{{#`eOHo%;n=ra_e-1PQD9Pp>W@dbZ!+IaCKaIs@CoH>`8q5_{VFx!` z5DK^Z6`Y)F+18)D+WBx^TG{y1WQc7lXx>L=BdHPf(_9a{c8me9vBMxHgG8oWhx04f z`ZwK`-<#6V*kiA1IOEq{3WrxT#D9NK`4NOwxELxnHxC4{@lfhx zm+>r*a{8MjuA+3W!EaPUR>_`yO4#VtDdvvs(p&^of_U52Tzjcf1AG20PQyJSo9eYk@B|D-;TaGHLHu z>;8@8)02NGyiiT^VPMe{+#fnG3Ipw0f!)C`pU? zhqB-{Q#N4uI4`V}>rnGob#5)YMoBuiMdd1CJ6V?z*REL$t>TS$F1Z-xOgomY0wz%z zmkhS1bf6r@&$JX}sx%h3V9kzcHmrO@{9;hVjx$dKq;lU&F-Q5cop$euz!)qglCA#% z;GK2$MCbN%u_y zl=z%h%3ALBBfS-) zOse?XrdvtXM#oF25$Xlz((eIB8*S59v38P=0y3eZoB5(%c?DKF+?QaWnSboa>SUWS zyNgD1gZfW7t9J+<`o95YOamppEkir?rmS`6R7p#uH+-Og1m+~a4b764gPlk?VY;bhS%I{y>>hDwx9a8yoSAWREjXM@92VLh;_GqAmzMKU_x`Hblnj{a( zcMzk}W1>+ERZee+)d5HscnjrCU}q^1?IqpE8+jAFyb~3un8UL#3P=M&Y$XaAjlW~i z2P5sPz@nQvbY}!{p=EP{9HQzf|48d1D1dk{rrRkuTrzYX1t5vwjD@8z3XQRXSlDV%%N`2&gcuSK(9}K$DF*sb?D|)} zI`W7~_$ua9LZixwO=p74!!ek)+aMAK=0f)GceE`a*KrU;1%<~Ypv_=GQ5o-kff#9u zW&Ib0ab@uyVbrl#(5`V#rtl@+FXDH;jq>3#12m6-;RvgheRLpH7hI}UqsyrPbbbA& zXtT%b-nGygc}OsniA9NG{< z^_z|PA5@cZz#SL$Rj@*La5@t)&S&67gQG|LBqgC#v_bFX?$&TiKDv_o2+^=kzr@=U z@iCtd_vvPZIS#D~^+DB6D?>1cV|qUftcL$$%?zFa;%?Q3N-VI#6cPt(0(s&z$TP$? zUBML-04{;vfph;tuFbFoGiO{xfOZ*guf~$7s=nYzv=FqI(A!)=VZd~qYW%y3P{o!s zMWWWuV#cz_*>x*~qfpMxySu$Qaoy_K2TRR~iR@Z`>3U^A+GNH1Jm;_N^&^q7U?4kI z-tD9a;MN&9Hjx5b;TlTbNCP7Og~N$wdlPF$QedKjCJua}dew~YO}ER_6BO7gh51m5 z>HnB&VVr#GyRC>T#eoIx1iJ5OARDxjx4M zqb)PcL5P_nq>K#1Q$rspm3HCSERYw3+k=&?54Xs?sEd=U4`*g+nP$Wl1xEn~Aab{u zT`)cic{EKf+ClyKfP4hK*1?`lk3ma8u)MGd<2z)w zzt{*`o%x^i?~)xqL7)11qVYF_%$ey|m8@9##zbcrT!MUhM;)Q=8(ew~Q$<9ba%|)R_d#>>hfBy^*vAy%< z)o~l1VWw?z?r}*USOD7Qlu^Cd+B@s6X~}`}ktN}q?K1eCn@6B3E|4|kDtii(Dz)v1 zm4Z5tR6(yFKbWIpm(c9YJNqH}vZq)n&F5z4-dmu$tTuBlp0(S_PSew3m;D3-ph;hAB;eAY7!6BB6H;{6k!lC+QsABhy_*pdnvo4 zu{}n??4X1`2E_V&0I)VH*jYdq{dg(r%p&OwCr07~~Xt2koasPQ^w)D3M zP7t$wFmn~+n7!3-P695obZF;rx!XI5FZ8va^^0MHijni6;r%yMY7Tbi607VtS^EO& zEE;%Ny8II2>veEwItBxW_y&dCj{*)1(99t2gG0<~BFqnQASetyp1zKU94%@(ClvXr*Bu}sN2 zF;kMtHYrQC8I*nB4Kwq-zW>IjpYHQ)pR3Ey_S5CC>QwlFb1ABvdlHG1tj945k{I;r)QVw_{8Oth_cqeLiDj;~MU zK?f*77Lq4qPNeVqtgZ%nIcAvZ1v5E8UK3q%#~q&sUU98eRG}XGASWrGxciOrsb5W% zN5~$-%}-W`p5TYs%QY>Etj368_S$Re`E7^-~3I6zeMprL!vE;I9e1XJX+Aj)q1-qHXX%NDNGJe zVQxcahb@fo4msZT{-R_xebvw@`S+)wvb&C0EP&c^ycSh3S37xkLQVNDGc>dU0^~>@ zmelo=Ly(WXg;G8pgRpXH7^)#s*Lwz(vMl!{BIQ@LLEJU6$WQl51<3Zh=DDZO+?)LW z1Bt@F7qdTJWAybkhPs`*Y&H>`brL9`Dx^9m{fRo)r!A5$a`{r&TP(u={_FNl>dU1e zB)1L?3ETFR16t-l;(wJZZ`8R7<+yd!m)H{;TY)z6k7q8C;@+BsT|axO#SvC+YwFw! zWg1*l@z^nabs*pCktyS`Z6zx~{IXS@Iu;oBO_hnRt`2a^LMa=22~q{L0FbD0=FL>`3+bXO*AbVss{>x)(a_n`KYU$#jp~;cJ4or|=+zgN4|O|t z^41etCi^tlH37pz?y=#1HFkZip2h^JE&lFuQUD%T{^Vm7JEnDsHDA6g@a&})FWCR^ zKtpBJnx%{z6`uos-&NKF@sqV6@e;c_&tZ9)P)3%Jvf(jD~8*Og$QdnxEglGC);{I!oUz4OqTv{-ALg zC%k!Yot^|53fMtTz8^NqR_D^Ufw_ww0NtdTUxym33oInAW@25Rz_1@n1p5{ByHVo)4NY{=FT{lpB} zjH#KO;22C+NS>%z&>gRtDmLd#**U*|9RD?2+NWmW6Z5c*HGt_TS&%3%8AgIuE&0|r7q%C{HrRje?@_BaQO@;ZN_ z@s|ov1-d)%GGDOA*umEd!s$kWIggG=ijs>{nO0li3X-5w?PK$;mF;!uXURRx>sNLq z&i?^=O}>jpzOD&v;>wMXk4vS{P%fZHN%P&zvmUnma}?nbDn?Q96WSFr;Umvdbz9?3 z9yx$yTEB50bImlc737ud{sv+0w0;d3!vd3cf2Hw5P{~s0-JFJ1`|T(~!s+JGfo?(C ze^zbBo4O&oW3d_8`sCXrpwlm?xh>k|LnQbH8R1mEWCOf=8=3-8Ji&iBv^L3seDdl~Jv$_#O^ap#I ze_Jq%s({e^dlBv!2U+LHKkYWg<>;Tdl=c0Q<)$FfPHAoOvV4kDVLq2JU+r=sxLPyh z5(1%J=of6Bj%X__SVKAR=ni{+odX;Xb(MUaOceYU?DF-aOPVpq6ka}5+B#r)?Cu@; zVXHI3D<&h_lkXO8d;J-?87k~=G1e`pjcap-UV*pO+YIiHB@5YgWYuZe1CC|5ZWqf* z!^uwsGF67;wdiu$#x~f(8LaZD4|G}iP*>+oz1oiA6(CWe6B)nJPJ|I zDuRH{Hh;P@%awpv7d~lrD)}NeeTv{Vye!4KQ2So&XTXro0h&e3-R|2x6Q8p(y{Y4U zx_u?w;zG4?#;z8iot<~cxnD=<=x+NmgQhCR1nOt#65T~$k(6#wcSsz1mEV*Ku#X%3 z842-TyTR#Z8;onbG>TfQ4K%z~)Lr-a*S9K#SLq41ion)C5aKkjoisRc#8zN%3_%ka zn92a@msvZOv&hl-q3?sc8mbh^%g6IiZ*c3I;-Nq3QQqZ*Fxq!o&vbMAC~0j;z_JA< zGDBrNU0mO;g@?^B6ps3wU0k31!g%acnB$|9rAKRH$eaM)(Rz5_5~3QO)w!kJ);ZuG znm-oP-JWoq%IQs9R(GY**sG_jFVQHn_DDsNUTwo6Uo9O|ojvb-A((Z8C^|@2& z)6P>X)AL$}9RUrxj8W=KrM9`uoke`XT$aF||F%5<1#-L;HVfs-ry?vVJ&IPD%>FU< zc^pvYo49^`yzEOL>7bTKRtY^4F4k!9#`3Q^^bhCVJq>h$C9_;i^Z z(f6mpIH#t+iC2r7wBTS6?8O`kxs<9Kx>OZX9j^zX?#1E z%C_r>K2%x5z@A*Er2g`0e(ki5U=y32jYJ1YkG$oQkK8ZRe2+Xhq}=HasUWYs)!#~~ z2h;f9Rbl|Stc5u!QyD9i%F*{UM4S?s7%bxYj02wC|9B2EMyB<0D!%1WM! zW_kS>-(JecFeQK@r7%&?mMf3x+1S&AW3Mlxn%BJp3aQcd1$eP}DXSyS^Iq6)h8U~H zRA635h{lKimeLFCZMk{KtP~_->I~~=Vh0Q_l_hBrzq`=tLE?c3OZ3(}@_-E5e%X$N zG!n9}675z&Hr-sgINRFm&;N=lSWA^&K1}fdBH!)pvz0rInV3lswO^La)5?Z(cQ9;9 z+^CbkccheZg&f7Bt?K1oi5)&#vq{KOy!rCG>9=4z&y8o9?F2kgz7!hkD|N7AH)bp8 z)(^5YZHgRtDQZ8079mmPLeU-}xivgOWiujI&#%<`Me9w4-L4k*>G4KK97I@o2i`O; z%u2Y{n<>M1cJ=+H71~9R!+Kt0e#1D@fI1UTb(r0MFnjGjifKKo)`y8$a2?!)j-6F6 zQTVS_hCNIO%BcWz=@%j$oNs=(Em58xHQlsaR^E%FsaTE1Gwg2Cd*M?znTmGk6izou05A~D5lmG5pcQAvUOz2O$?f{tt@*Wc42u9t|I)31^n3L z|F^au2ZS8b^jnU?&+rk7>~yj5(WxmZMmF`@o?uZlRK+d*OnoRO9b6}6Bw0{PLLr%H z|BfVmHQekuf-3x%QOs}hTduyV%UF$KJ6&b>Nx6iJcr&69R^cLpj?qbvX49iXUY!!^ z3+tfHI6d*fmqS2?L(i(&7QILEM&Z+gl9t0(c=5y)V}hZ`vr=+u#DjqGM@LU=bQP#l zSl~s-!5m@hZ8~NU^BXc^vq|wSH0124Gquluu(_!yvr99cIg*3giE^(FMnj8BpC3x% z2t>H~psiLMD-FYH5F~C?TyO&;1MWb)o6*S;{yyhfEz#Th=X&PwH__9oM6V2N$J34M zc8A|HBR{r`ok@nLFNMFpOMmFPD0W1RT;RqaVZv3xo*uF6mNk}o< z7YoBkcqQrf?<`|Ii(B)MX3{ekS$WpYp*V!LQkl(*Ei)n%o3iS=&rpP&k=Gu=R^*%r zK%&XmAY>L3BnJ+Pt?alH^9oSq$SK0D7gq!VTS>pF1Vdv2u*JXVR>ZbXUP=yF`%BDW z0%ioRpds6SO}?uA%`*)VUxf_Os`w607m7*LKO%)A?YgeVQKxuH}qSPHtPAbQJoCahhb;ZGrCYt63 zM=Lm^^nG2$!ExysOfYK(qvc#}Eg(N5xu~(yV3VlyO%mUC>P}2kEzRJLih`P_ufQI5# zeOtvLeswB>*EC_Ao5$oxfOC`4=KZ}*NM@1QaQ8pC_=lTB8*#)%gg{NBR5zN-^&5XsBZp8s$djGun84$?U7 z6U6K2GN8%wlp(%xG?r7ijs2@Ode;E_gNXKXg(5SU}p zglp|W&Y*rmOEVJPs6-si`S=SDy}6Jn8)GN70@?vFz*&Og1Sfrj9+ zID{oCZIC?l5N*$u^|=AqW7*Sajd-Jmuk0}0A(6Sn7>`kfo3l@1(NN%mSy#vYV@k&Q zr!`>r9tOdO%;%EXn+je^YPz0;r+QFTovZ;|Pd!7ARluAcA0KIeg)BY5DA*R^f6|j5 zP>gQ!n@Zdef0ewQ{Y;RE{A98DO&n#26=mZoh2(QcG*23HWAtbHk<3p_n;p(lr=N~xc)+u@FdeShA!yI} zmBYJIkWC7xwVD3P;Zh@t3hc3p)CIl^aQ;^rFQHGFq98j>uP}PZWb+3dY<6}9cx=G} z_`uE+;tr*jb~PkIxuZc$K4tKn9ll=J=_Yx{2=gR2%jl2+tH~7P;4&@IX*^XSTnuXtK z0u>qBh>Gue>RS|3I_`!bx__-BK24-UPm%p*%6y8H(*>tlVQ2TZSQf8z4QkA;@QYBQblx~D~^7x6^Z_*trFbXaZKU$ zqu>XMz}Se*q+QtH!{{RykL9P(+H;;r=w!ge+Xzi;$wEH6b)&S#X3h8A4JJN$S2g2P zSXL_Y2H344y&-;cUQyG@g6j7aYmfpnDrab45=^dpDBE8cUx8^JhqWUBpC${x!O#j0 zyXmf2`0gE8LI##>HzZ9}3=JtJ8^g`?PjF8@Ae{OS47b3R?$zU9jvRo8E2=NZ!!RD^ zurF6qVCw=vhOp7GH3uZ8U1_7#ba>_8}I>JIyUcPTbNUUKY z3B$nZfP+sjce))mWHwh72gDy0hK2L5n}4JEcW?epq5l~|q6kFVC#h|dye}s3&sxC3 M^sGtIF=EXB0l5mc!~g&Q literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/rotate_extrude_dxf-tests-expected.png b/tests/regression/throwntogethertest/rotate_extrude_dxf-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..b338118a6371bb49b59129370fa2e5d61e6de337 GIT binary patch literal 7635 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 M^sGtIF=EXB0l5mc!~g&Q literal 0 HcmV?d00001 From e6ec07a08d15c8ca0ecf6ad4a66429285df3fef7 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Mon, 31 Oct 2011 21:36:55 +0100 Subject: [PATCH 90/93] bugfix: filenames with spaces caused a syntax error with the use keyword (fixes #31) --- src/lexer.l | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lexer.l b/src/lexer.l index cf5cb6d8..d9ccd769 100644 --- a/src/lexer.l +++ b/src/lexer.l @@ -100,7 +100,7 @@ include[ \t\r\n>]*"<" { BEGIN(include); } } -use[ \t\r\n>]*"<"[^ \t\r\n>]+">" { +use[ \t\r\n>]*"<"[^\t\r\n>]+">" { QString filename(yytext); filename.remove(QRegExp("^use[ \t\r\n>]*<")); filename.remove(QRegExp(">$")); From 025eca84e74fd88db4d920083f17c3716a2ca5b3 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Mon, 31 Oct 2011 23:53:53 +0100 Subject: [PATCH 91/93] Pass flag to QApplication to tell it we're a cmd-line tool --- tests/csgtestcore.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/csgtestcore.cc b/tests/csgtestcore.cc index 6c3a7d0f..2390f062 100644 --- a/tests/csgtestcore.cc +++ b/tests/csgtestcore.cc @@ -80,7 +80,7 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type) initialize_builtin_functions(); initialize_builtin_modules(); - QApplication app(argc, argv); + QApplication app(argc, argv, false); QDir original_path = QDir::current(); From 759446d6c3b8f10a95c6d4bf74f9b95a5821565b Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Mon, 31 Oct 2011 23:54:16 +0100 Subject: [PATCH 92/93] sync --- doc/TODO.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/TODO.txt b/doc/TODO.txt index 2202792e..26dd6c83 100644 --- a/doc/TODO.txt +++ b/doc/TODO.txt @@ -217,7 +217,6 @@ TESTING o Caching and MDI looks suspicious when the code relies on external resources which might be loaded from difference locations in different documents -> we might get a false cache hit -o Are contructs like "child(0)" cached? Could this give false cache hits? o Collect "all" available OpenSCAD scripts from the internets and run the integration tests on them all o dumptest tests: From cb56f700b1b0f4ae589da62a5fd1d4e368deb604 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Mon, 31 Oct 2011 23:56:35 +0100 Subject: [PATCH 93/93] de-Qt'ified printutils --- src/MainWindow.h | 4 ++-- src/PolySetCache.cc | 2 +- src/PolySetCache.h | 2 +- src/parser.y | 2 +- src/printutils.cc | 35 ++++++++++++++++++----------------- src/printutils.h | 20 ++++++++++---------- tests/echotest.cc | 4 ++-- 7 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/MainWindow.h b/src/MainWindow.h index a3c812b9..06332b0c 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -79,8 +79,8 @@ private: bool maybeSave(); bool checkModified(); QString dumpCSGTree(AbstractNode *root); - static void consoleOutput(const QString &msg, void *userdata) { - static_cast(userdata)->console->append(msg); + static void consoleOutput(const std::string &msg, void *userdata) { + static_cast(userdata)->console->append(QString::fromStdString(msg)); } void loadViewSettings(); void loadDesignSettings(); diff --git a/src/PolySetCache.cc b/src/PolySetCache.cc index 0a936423..2a2da9c6 100644 --- a/src/PolySetCache.cc +++ b/src/PolySetCache.cc @@ -17,5 +17,5 @@ void PolySetCache::print() PolySetCache::cache_entry::cache_entry(const shared_ptr &ps) : ps(ps) { - if (print_messages_stack.size() > 0) this->msg = print_messages_stack.last(); + if (print_messages_stack.size() > 0) this->msg = print_messages_stack.back(); } diff --git a/src/PolySetCache.h b/src/PolySetCache.h index da51c5e6..d1efeb70 100644 --- a/src/PolySetCache.h +++ b/src/PolySetCache.h @@ -23,7 +23,7 @@ private: struct cache_entry { shared_ptr ps; - QString msg; + std::string msg; cache_entry(const shared_ptr &ps); ~cache_entry() { } }; diff --git a/src/parser.y b/src/parser.y index 33fbc3f4..d703a813 100644 --- a/src/parser.y +++ b/src/parser.y @@ -649,7 +649,7 @@ Module *Module::compile_library(std::string filename) if (lib_mod) { libs_cache[filename].mod = lib_mod; - libs_cache[filename].msg = print_messages_stack.last().toStdString(); + libs_cache[filename].msg = print_messages_stack.back(); } else { libs_cache.erase(filename); } diff --git a/src/printutils.cc b/src/printutils.cc index a315ab3f..ec417656 100644 --- a/src/printutils.cc +++ b/src/printutils.cc @@ -2,7 +2,7 @@ #include #include -QList print_messages_stack; +std::list print_messages_stack; OutputHandlerFunc *outputhandler = NULL; void *outputhandler_data = NULL; @@ -14,37 +14,38 @@ void set_output_handler(OutputHandlerFunc *newhandler, void *userdata) void print_messages_push() { - print_messages_stack.append(QString()); + print_messages_stack.push_back(std::string()); } void print_messages_pop() { - QString msg = print_messages_stack.takeLast(); - if (print_messages_stack.size() > 0 && !msg.isNull()) { - if (!print_messages_stack.last().isEmpty()) - print_messages_stack.last() += "\n"; - print_messages_stack.last() += msg; + std::string msg = print_messages_stack.back(); + print_messages_stack.pop_back(); + if (print_messages_stack.size() > 0 && !msg.empty()) { + if (!print_messages_stack.back().empty()) { + print_messages_stack.back() += "\n"; + } + print_messages_stack.back() += msg; } } -void PRINT(const QString &msg) +void PRINT(const std::string &msg) { - if (msg.isNull()) - return; + if (msg.empty()) return; if (print_messages_stack.size() > 0) { - if (!print_messages_stack.last().isEmpty()) - print_messages_stack.last() += "\n"; - print_messages_stack.last() += msg; + if (!print_messages_stack.back().empty()) { + print_messages_stack.back() += "\n"; + } + print_messages_stack.back() += msg; } PRINT_NOCACHE(msg); } -void PRINT_NOCACHE(const QString &msg) +void PRINT_NOCACHE(const std::string &msg) { - if (msg.isNull()) - return; + if (msg.empty()) return; if (!outputhandler) { - fprintf(stderr, "%s\n", msg.toUtf8().data()); + fprintf(stderr, "%s\n", msg.c_str()); } else { outputhandler(msg, outputhandler_data); } diff --git a/src/printutils.h b/src/printutils.h index 60cd12aa..761e6c87 100644 --- a/src/printutils.h +++ b/src/printutils.h @@ -1,28 +1,28 @@ #ifndef PRINTUTILS_H_ #define PRINTUTILS_H_ -#include -#include +#include +#include #include #include -typedef void (OutputHandlerFunc)(const QString &msg, void *userdata); +typedef void (OutputHandlerFunc)(const std::string &msg, void *userdata); extern OutputHandlerFunc *outputhandler; extern void *outputhandler_data; void set_output_handler(OutputHandlerFunc *newhandler, void *userdata); -extern QList print_messages_stack; +extern std::list print_messages_stack; void print_messages_push(); void print_messages_pop(); -void PRINT(const QString &msg); -#define PRINTF(_fmt, ...) do { QString _m; _m.sprintf(_fmt, ##__VA_ARGS__); PRINT(_m); } while (0) -#define PRINTA(_fmt, ...) do { QString _m = QString(_fmt).arg(__VA_ARGS__); PRINT(_m); } while (0) +void PRINT(const std::string &msg); +#define PRINTF(_fmt, ...) do { QString _m; _m.sprintf(_fmt, ##__VA_ARGS__); PRINT(_m.toStdString()); } while (0) +#define PRINTA(_fmt, ...) do { QString _m = QString(_fmt).arg(__VA_ARGS__); PRINT(_m.toStdString()); } while (0) -void PRINT_NOCACHE(const QString &msg); -#define PRINTF_NOCACHE(_fmt, ...) do { QString _m; _m.sprintf(_fmt, ##__VA_ARGS__); PRINT_NOCACHE(_m); } while (0) -#define PRINTA_NOCACHE(_fmt, ...) do { QString _m = QString(_fmt).arg(__VA_ARGS__); PRINT_NOCACHE(_m); } while (0) +void PRINT_NOCACHE(const std::string &msg); +#define PRINTF_NOCACHE(_fmt, ...) do { QString _m; _m.sprintf(_fmt, ##__VA_ARGS__); PRINT_NOCACHE(_m.toStdString()); } while (0) +#define PRINTA_NOCACHE(_fmt, ...) do { QString _m = QString(_fmt).arg(__VA_ARGS__); PRINT_NOCACHE(_m.toStdString()); } while (0) std::ostream &operator<<(std::ostream &os, const QFileInfo &fi); diff --git a/tests/echotest.cc b/tests/echotest.cc index 5b216f4d..046f8830 100644 --- a/tests/echotest.cc +++ b/tests/echotest.cc @@ -52,9 +52,9 @@ QString currentdir; QString examplesdir; QString librarydir; -static void outfile_handler(const QString &msg, void *userdata) { +static void outfile_handler(const std::string &msg, void *userdata) { std::ostream *str = static_cast(userdata); - *str << msg.toUtf8().data() << std::endl; + *str << msg << std::endl; } int main(int argc, char **argv)