diff options
author | Don Bright <hugh.m.bright@gmail.com> | 2011-11-26 18:17:54 (GMT) |
---|---|---|
committer | Don Bright <hugh.m.bright@gmail.com> | 2011-11-26 18:17:54 (GMT) |
commit | 8197294d4c30d533c88ef2901e298d2b2863aa87 (patch) | |
tree | 7f26004c4b4d1ae12970913ea4d0201be5c50088 /tests | |
parent | 2bd48629ef8482a957f7db0921a3b3c7fdc458e1 (diff) | |
parent | 14b4ed4b5baa0efa7eea90322faca4fecfbfa294 (diff) |
Merge branch 'cakebaby' of github.com:donbright/openscad into cakebaby
Conflicts:
tests/CMakeLists.txt
Diffstat (limited to 'tests')
-rw-r--r-- | tests/CMakeLists.txt | 31 | ||||
-rw-r--r-- | tests/regression/cgalpngtest/polygon-tests-expected.png | bin | 8240 -> 7755 bytes | |||
-rw-r--r-- | tests/regression/dumptest/escape-test-expected.txt | 2 | ||||
-rw-r--r-- | tests/regression/dumptest/polygon-tests-expected.txt | 3 | ||||
-rw-r--r-- | tests/regression/echotest/builtin-tests-expected.txt | 2 | ||||
-rw-r--r-- | tests/regression/echotest/dim-all-expected.txt | 12 | ||||
-rw-r--r-- | tests/regression/opencsgtest/polygon-tests-expected.png | bin | 9057 -> 8736 bytes | |||
-rw-r--r-- | tests/regression/throwntogethertest/polygon-tests-expected.png | bin | 4769 -> 8797 bytes | |||
-rwxr-xr-x | tests/test_cmdline_tool.py | 30 | ||||
-rwxr-xr-x | tests/test_pretty_print.py | 96 | ||||
-rw-r--r-- | tests/yee_compare.cpp | 681 | ||||
-rw-r--r-- | tests/yee_compare.h | 126 |
12 files changed, 108 insertions, 875 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f57e40e..f604f73 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -143,10 +143,10 @@ if (NOT OPENCSG_INCLUDE_DIR) message(STATUS "OPENCSG_DIR: " ${OPENCSG_DIR}) find_path(OPENCSG_INCLUDE_DIR opencsg.h - PATHS ${OPENCSG_DIR}/include) + HINTS ${OPENCSG_DIR}/include) find_library(OPENCSG_LIBRARY opencsg - PATHS ${OPENCSG_DIR}/lib) + HINTS ${OPENCSG_DIR}/lib) if (NOT OPENCSG_INCLUDE_DIR OR NOT OPENCSG_LIBRARY) message(FATAL_ERROR "OpenCSG not found") else() @@ -187,18 +187,35 @@ BISON_TARGET(OpenSCADparser ../src/parser.y ${CMAKE_CURRENT_BINARY_DIR}/parser_y ADD_FLEX_BISON_DEPENDENCY(OpenSCADlexer OpenSCADparser) set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/parser_yacc.c PROPERTIES LANGUAGE "CXX") -if (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") +# CGAL + +if (NOT $ENV{CGAL_DIR} STREQUAL "") + set(CGAL_DIR "$ENV{CGAL_DIR}") +elseif (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") set(CGAL_DIR "$ENV{MACOSX_DEPLOY_DIR}/lib/CGAL") set(CMAKE_MODULE_PATH "${CGAL_DIR}") endif() find_package(CGAL REQUIRED) message(STATUS "CGAL found in ${CGAL_USE_FILE} ${CGAL_INCLUDE_DIRS} ${CGAL_LIBRARIES_DIR}") +if("${CGAL_MAJOR_VERSION}.${CGAL_MINOR_VERSION}" VERSION_LESS 3.6) + message(FATAL_ERROR "CGAL >= 3.6 required") +endif() include_directories(${CGAL_INCLUDE_DIRS}) +# Imagemagick + +find_package(ImageMagick COMPONENTS convert) +if (ImageMagick_convert_FOUND) + message(STATUS "ImageMagick convert executable found: " ${ImageMagick_convert_EXECUTABLE}) +else() + message(FATAL_ERROR "Couldn't find imagemagick 'convert' program") +endif() + # Internal includes include_directories(../src) add_definitions(-DOPENSCAD_VERSION=test -DOPENSCAD_YEAR=2011 -DOPENSCAD_MONTH=10) +add_definitions(-DOPENSCAD_TESTING) set(CORE_SOURCES tests-common.cc @@ -294,12 +311,6 @@ add_executable(echotest echotest.cc) target_link_libraries(echotest tests-nocgal tests-core ${QT_LIBRARIES} ${OPENGL_LIBRARY} ${Boost_LIBRARIES}) # -# Yangli Hector Yee's PerceptualDiff code -# - -add_executable(yee_compare yee_compare.cpp lodepng.cpp) - -# # dumptest # add_executable(dumptest dumptest.cc) @@ -357,7 +368,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}" ${PYTHON_EXECUTABLE} ${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 -c ${ImageMagick_convert_EXECUTABLE} -s ${TESTSUFFIX} ${CMAKE_BINARY_DIR}/${TESTCMD} "${SCADFILE}" ) endforeach() endfunction() diff --git a/tests/regression/cgalpngtest/polygon-tests-expected.png b/tests/regression/cgalpngtest/polygon-tests-expected.png Binary files differindex 89aed2f..5ceabe8 100644 --- a/tests/regression/cgalpngtest/polygon-tests-expected.png +++ b/tests/regression/cgalpngtest/polygon-tests-expected.png diff --git a/tests/regression/dumptest/escape-test-expected.txt b/tests/regression/dumptest/escape-test-expected.txt index bea0156..2097dc4 100644 --- a/tests/regression/dumptest/escape-test-expected.txt +++ b/tests/regression/dumptest/escape-test-expected.txt @@ -1,2 +1,2 @@ - import(file = "A-\\ B-\" C-\t D-\n E-' F-\\\\", layer = "A:\\ B:\" C:\t D:\n E:' F:\\\\", origin = [0, 0], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 1); + import(file = "B-\" C-\t D-\n E-'", layer = "A:\\ B:\" C:\t D:\n E:' F:\\\\", origin = [0, 0], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 1); diff --git a/tests/regression/dumptest/polygon-tests-expected.txt b/tests/regression/dumptest/polygon-tests-expected.txt index ce4ad87..be66d21 100644 --- a/tests/regression/dumptest/polygon-tests-expected.txt +++ b/tests/regression/dumptest/polygon-tests-expected.txt @@ -29,4 +29,7 @@ multmatrix([[1, 0, 0, -2], [0, 1, 0, -2], [0, 0, 1, 0], [0, 0, 0, 1]]) { polygon(points = [[0, 0], [0.5, -0.2], [1, 0], [1.2, 0.5], [1, 1], [0.5, 1.2], [0, 1], [-0.2, 0.5]], paths = [[0, 1, 2, 3], [4, 5, 6, 7]], convexity = 1); } + multmatrix([[1, 0, 0, 2], [0, 1, 0, -4], [0, 0, 1, 0], [0, 0, 0, 1]]) { + polygon(points = [[0, 0], [1, 0], [1, 1], [0, 0]], paths = undef, convexity = 1); + } diff --git a/tests/regression/echotest/builtin-tests-expected.txt b/tests/regression/echotest/builtin-tests-expected.txt index e8b2027..0e8d1a7 100644 --- a/tests/regression/echotest/builtin-tests-expected.txt +++ b/tests/regression/echotest/builtin-tests-expected.txt @@ -1 +1 @@ -ECHO: 3.141592653589793 +ECHO: 3.14159265358979 diff --git a/tests/regression/echotest/dim-all-expected.txt b/tests/regression/echotest/dim-all-expected.txt index 74044a1..d8c3269 100644 --- a/tests/regression/echotest/dim-all-expected.txt +++ b/tests/regression/echotest/dim-all-expected.txt @@ -1,16 +1,16 @@ WARNING: Unsupported DXF Entity `LEADER' (1) in `dim-all.dxf'. -ECHO: linearX = 51.44957554275265 +ECHO: linearX = 51.4495755427526 WARNING: Unsupported DXF Entity `LEADER' (1) in `dim-all.dxf'. -ECHO: linearY = 29.13025467434841 +ECHO: linearY = 29.1302546743484 WARNING: Unsupported DXF Entity `LEADER' (1) in `dim-all.dxf'. -ECHO: aligned = 60.00000000000001 +ECHO: aligned = 60.0000000000000 WARNING: Unsupported DXF Entity `LEADER' (1) in `dim-all.dxf'. -ECHO: ordinateX = -49.17542445724735 +ECHO: ordinateX = -49.175424457247 WARNING: Unsupported DXF Entity `LEADER' (1) in `dim-all.dxf'. -ECHO: ordinateY = 30.86974532565159 +ECHO: ordinateY = 30.8697453256515 WARNING: Unsupported DXF Entity `LEADER' (1) in `dim-all.dxf'. ECHO: radius = 60 WARNING: Unsupported DXF Entity `LEADER' (1) in `dim-all.dxf'. ECHO: diameter = 120 WARNING: Unsupported DXF Entity `LEADER' (1) in `dim-all.dxf'. -ECHO: arc = 59.03624346792648 +ECHO: arc = 59.0362434679264 diff --git a/tests/regression/opencsgtest/polygon-tests-expected.png b/tests/regression/opencsgtest/polygon-tests-expected.png Binary files differindex 4d88973..fe84a80 100644 --- a/tests/regression/opencsgtest/polygon-tests-expected.png +++ b/tests/regression/opencsgtest/polygon-tests-expected.png diff --git a/tests/regression/throwntogethertest/polygon-tests-expected.png b/tests/regression/throwntogethertest/polygon-tests-expected.png Binary files differindex c9cdb36..779b878 100644 --- a/tests/regression/throwntogethertest/polygon-tests-expected.png +++ b/tests/regression/throwntogethertest/polygon-tests-expected.png diff --git a/tests/test_cmdline_tool.py b/tests/test_cmdline_tool.py index 40aa4d6..ebe802e 100755 --- a/tests/test_cmdline_tool.py +++ b/tests/test_cmdline_tool.py @@ -49,13 +49,15 @@ def execute_and_redirect(cmd, params, outfile): retval = -1 try: proc = subprocess.Popen([cmd] + params, stdout=outfile) + out = proc.communicate()[0] retval = proc.wait() 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 - return retval + if outfile == subprocess.PIPE: return (retval, out) + else: return retval def get_normalized_text(filename): text = open(filename).read() @@ -74,15 +76,28 @@ def compare_default(resultfilename): return True def compare_png(resultfilename): - print >> sys.stderr, 'Yee image compare:' + #args = [expectedfilename, resultfilename, "-alpha", "Off", "-compose", "difference", "-composite", "-threshold", "10%", "-blur", "2", "-threshold", "30%", "-format", "%[fx:w*h*mean]", "info:"] + #args = [expectedfilename, resultfilename, "-alpha", "Off", "-compose", "difference", "-composite", "-threshold", "10%", "-morphology", "Erode", "Square", "-format", "%[fx:w*h*mean]", "info:"] + # 'morphology' is only available in newer versions of ImageMagick. + # http://www.imagemagick.org/Usage/morphology/#alturnative + args = [expectedfilename, resultfilename, "-alpha", "Off", + "-compose", "difference", "-composite", "-threshold", "10%", + #"-morphology", "Erode", "Square", + "-gaussian-blur","3x65535", "-threshold","99.999%", + "-format", "%[fx:w*h*mean]", "info:"] + print >> sys.stderr, 'ImageMagick image comparison: convert ', ' '.join(args[2:]) print >> sys.stderr, ' expected image: ', expectedfilename if not resultfilename: print >> sys.stderr, "Error: OpenSCAD did not generate an image to test" return False print >> sys.stderr, ' actual image: ', resultfilename - if execute_and_redirect("./yee_compare", [expectedfilename, resultfilename, "-downsample", "2", "-threshold", "300"], sys.stderr) != 0: - return False - return True + + (retval, output) = execute_and_redirect(options.convert_exec, args, subprocess.PIPE) + if retval == 0: + pixelerr = int(float(output.strip())) + if pixelerr < 32: return True + else: print >> sys.stderr, pixelerr, ' pixel errors' + return False def compare_with_expected(resultfilename): if not options.generate: @@ -135,11 +150,12 @@ def usage(): print >> sys.stderr, " -g, --generate Generate expected output for the given tests" print >> sys.stderr, " -s, --suffix=<suffix> Write -expected and -actual files with the given suffix instead of .txt" print >> sys.stderr, " -t, --test=<name> Specify test name instead of deducting it from the argument" + print >> sys.stderr, " -c, --convexec=<name> Path to ImageMagick 'convert' executable" if __name__ == '__main__': # Handle command-line arguments try: - opts, args = getopt.getopt(sys.argv[1:], "gs:t:", ["generate", "suffix=", "test="]) + opts, args = getopt.getopt(sys.argv[1:], "gs:c:t:", ["generate", "convexec=", "suffix=", "test="]) except getopt.GetoptError, err: usage() sys.exit(2) @@ -157,6 +173,8 @@ if __name__ == '__main__': else: options.suffix = a elif o in ("-t", "--test"): options.testname = a + elif o in ("-c", "--convexec"): + options.convert_exec = os.path.normpath( a ) # <cmdline-tool> and <argument> if len(args) < 2: diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py index fb88c2f..4dd614b 100755 --- a/tests/test_pretty_print.py +++ b/tests/test_pretty_print.py @@ -76,7 +76,7 @@ def read_sysinfo(filename): data += read_gitinfo() - data += 'Image comparison: PerceptualDiff by H. Yee' + data += 'Image comparison: ImageMagick' data = data.strip() @@ -116,7 +116,7 @@ class Test: def parsetest(teststring): patterns = ["Test:(.*?)\n", # fullname "Test time =(.*?) sec\n", - "Test time.*?Test (Passed)", + "Test time.*?Test (Passed)", # pass/fail "Output:(.*?)<end of output>", 'Command:.*?-s" "(.*?)"', # type "actual .*?:(.*?)\n", @@ -147,7 +147,8 @@ def wikify_filename(testname,filename,sysid): return result.replace('/','_') -def towiki(wiki_rootpath, startdate, tests, enddate, sysinfo, sysid, testlog): +def towiki(wiki_rootpath, startdate, tests, enddate, sysinfo, sysid): + wiki_template = """ <h3>[[WIKI_ROOTPATH]] test run report</h3> @@ -165,14 +166,25 @@ end time : ENDDATE <br> '''Failed image tests''' -{| border=1 cellspacing=0 cellpadding=1 -! Testname !! expected output !! actual output <REPEAT1> +{| border=1 cellspacing=0 cellpadding=1 |- -| FTESTNAME || [[File:EXPECTEDFILE|thumb|250px]] || ACTUALFILE_WIKI -</REPEAT1> +| colspan=2 | FTESTNAME +|- +| Expected image || Actual image +|- +| [[File:EXPECTEDFILE|250px]] || ACTUALFILE_WIKI |} +<pre> +TESTLOG +</pre> + + + +</REPEAT1> + + '''Failed text tests''' {|border=1 cellspacing=0 cellpadding=1 @@ -183,47 +195,40 @@ end time : ENDDATE <br> </REPEAT2> |} -'''Test logs''' - -(excerpted from Testing/Temporary/LastTest.Log) - -<pre> -FAILED_TESTLOGS -</pre> """ - numpassed = len(filter(lambda x: x.passed, tests)) - percent = str(int(100.0*numpassed / len(tests))) + passed_tests = filter(lambda x: x.passed, tests) + failed_tests = filter(lambda x: not x.passed, tests) + percent = str(int(100.0*len(passed_tests) / len(tests))) manifest = {} s = wiki_template repeat1 = ezsearch('(<REPEAT1>.*?</REPEAT1>)',s) repeat2 = ezsearch('(<REPEAT2>.*?</REPEAT2>)',s) dic = { 'STARTDATE': startdate, 'ENDDATE': enddate, 'WIKI_ROOTPATH': wiki_rootpath, - 'SYSINFO': sysinfo, 'SYSID':sysid, 'LASTTESTLOG':testlog, - 'NUMTESTS':len(tests), 'NUMPASSED':numpassed, 'PERCENTPASSED':percent } + 'SYSINFO': sysinfo, 'SYSID':sysid, + 'NUMTESTS':len(tests), 'NUMPASSED':len(passed_tests), 'PERCENTPASSED':percent } for key in dic.keys(): - s = re.sub(key,str(dic[key]),s) + s = s.replace(key,str(dic[key])) testlogs = '' - for t in tests: - # if t.passed: noop - if not t.passed: - testlogs += '\n\n'+t.fulltestlog - if t.type=='txt': - newchunk = re.sub('FTEST_OUTPUTFILE',t.fullname,repeat2) - newchunk = re.sub('FTESTNAME',t.fullname,repeat2) - s = s.replace(repeat2, newchunk+repeat2) - elif t.type=='png': - manifest[t.actualfile] = wikify_filename(t.fullname,t.actualfile,sysid) - manifest[t.expectedfile] = wikify_filename(t.fullname,t.expectedfile,sysid) - if t.actualfile: - actualfile_wiki = '[[File:'+manifest[t.actualfile]+'|thumb|250px]]' - else: - actualfile_wiki = 'No file generated.<br/>See log for details' - newchunk = re.sub('FTESTNAME',t.fullname,repeat1) - newchunk = newchunk.replace('ACTUALFILE_WIKI',actualfile_wiki) - newchunk = newchunk.replace('EXPECTEDFILE',manifest[t.expectedfile]) - s = s.replace(repeat1, newchunk+repeat1) - s = s.replace('FAILED_TESTLOGS',testlogs) + for t in failed_tests: + testlogs += '\n\n'+t.fulltestlog + if t.type=='txt': + newchunk = re.sub('FTEST_OUTPUTFILE',t.fullname,repeat2) + newchunk = re.sub('FTESTNAME',t.fullname,repeat2) + s = s.replace(repeat2, newchunk+repeat2) + elif t.type=='png': + manifest[t.actualfile] = wikify_filename(t.fullname,t.actualfile,sysid) + manifest[t.expectedfile] = wikify_filename(t.fullname,t.expectedfile,sysid) + if t.actualfile: + actualfile_wiki = '[[File:'+manifest[t.actualfile]+'|250px]]' + else: + actualfile_wiki = 'No image generated.' + newchunk = re.sub('FTESTNAME',t.fullname,repeat1) + newchunk = newchunk.replace('ACTUALFILE_WIKI',actualfile_wiki) + newchunk = newchunk.replace('EXPECTEDFILE',manifest[t.expectedfile]) + newchunk = newchunk.replace('TESTLOG',t.fulltestlog) + s = s.replace(repeat1, newchunk+repeat1) + s = s.replace(repeat1,'') s = s.replace(repeat2,'') s = re.sub('<REPEAT.*?>\n','',s) @@ -253,7 +258,7 @@ def upload_dryrun(wikiurl,api_php_path,wikidata,manifest,wiki_rootpath,sysid,bot print 'save ', len(wikidata), ' bytes to page ',wiki_rootpath+sysid for localfile in manifest.keys(): if localfile: - localf=open(localfile) + localf=open(localfile,'rb') wikifile = manifest[localfile] print 'upload',localfile,wikifile @@ -289,7 +294,7 @@ def upload(wikiurl,api_php_path,wikidata,manifest,wiki_rootpath,sysid,botname,bo print 'upload images' for localfile in sorted(manifest.keys()): if localfile: - localf = open(localfile) + localf = open(localfile,'rb') wikifile = manifest[localfile] skip=False if 'expected.png' in wikifile.lower(): @@ -301,7 +306,10 @@ def upload(wikiurl,api_php_path,wikidata,manifest,wiki_rootpath,sysid,botname,bo print 'uploading',wikifile,'...' site.upload(localf,wikifile,wiki_rootpath + ' test', ignore=True) -wikisite = 'cakebaby.referata.com' +#wikisite = 'cakebaby.referata.com' +#wiki_api_path = '' +wikisite = 'cakebaby.wikia.com' +wiki_api_path = '/' wiki_rootpath = 'OpenSCAD' builddir = os.getcwd() logpath = os.path.join(builddir,'Testing','Temporary') @@ -314,11 +322,11 @@ def main(): sysinfo, sysid = read_sysinfo('sysinfo.txt') if '--forceupload' in sys.argv: forceupl=True else: forceupl=False - manifest, wikidata = towiki(wiki_rootpath, startdate, tests, enddate, sysinfo, sysid, testlog) + manifest, wikidata = towiki(wiki_rootpath, startdate, tests, enddate, sysinfo, sysid) trysave(wikidata, os.path.join(logpath,sysid+'.wiki')) htmldata = wikitohtml(wiki_rootpath, sysid, wikidata, manifest) trysave(htmldata, os.path.join(logpath,sysid+'.html')) if '--upload' in sys.argv: - upload(wikisite,'',wikidata,manifest,wiki_rootpath,sysid,'openscadbot','tobdacsnepo',dryrun=False,forceupload=forceupl) + upload(wikisite,wiki_api_path,wikidata,manifest,wiki_rootpath,sysid,'openscadbot','tobdacsnepo',dryrun=False,forceupload=forceupl) main() diff --git a/tests/yee_compare.cpp b/tests/yee_compare.cpp deleted file mode 100644 index 9de4720..0000000 --- a/tests/yee_compare.cpp +++ /dev/null @@ -1,681 +0,0 @@ -// modified from PerceptualDiff source for OpenSCAD, 2011 September - -#include "yee_compare.h" -#include "lodepng.h" -#include <cstdlib> -#include <cstring> -#include <cstdio> -#include <math.h> - -static const char* copyright = -"PerceptualDiff version 1.1.1, Copyright (C) 2006 Yangli Hector Yee\n\ -PerceptualDiff comes with ABSOLUTELY NO WARRANTY;\n\ -This is free software, and you are welcome\n\ -to redistribute it under certain conditions;\n\ -See the GPL page for details: http://www.gnu.org/copyleft/gpl.html\n\n"; - -static const char *usage = -"PeceptualDiff image1.tif image2.tif\n\n\ - Compares image1.tif and image2.tif using a perceptually based image metric\n\ - Options:\n\ -\t-verbose : Turns on verbose mode\n\ -\t-fov deg : Field of view in degrees (0.1 to 89.9)\n\ -\t-threshold p : #pixels p below which differences are ignored\n\ -\t-gamma g : Value to convert rgb into linear space (default 2.2)\n\ -\t-luminance l : White luminance (default 100.0 cdm^-2)\n\ -\t-luminanceonly : Only consider luminance; ignore chroma (color) in the comparison\n\ -\t-colorfactor : How much of color to use, 0.0 to 1.0, 0.0 = ignore color.\n\ -\t-downsample : How many powers of two to down sample the image.\n\ -\t-output o.ppm : Write difference to the file o.ppm\n\ -\n\ -\n Note: Input or Output files can also be in the PNG or JPG format or any format\ -\n that FreeImage supports.\ -\n"; - -CompareArgs::CompareArgs() -{ - ImgA = NULL; - ImgB = NULL; - ImgDiff = NULL; - Verbose = false; - LuminanceOnly = false; - FieldOfView = 45.0f; - Gamma = 2.2f; - ThresholdPixels = 100; - Luminance = 100.0f; - ColorFactor = 1.0f; - DownSample = 0; -} - -CompareArgs::~CompareArgs() -{ - if (ImgA) delete ImgA; - if (ImgB) delete ImgB; - if (ImgDiff) delete ImgDiff; -} - -bool CompareArgs::Parse_Args(int argc, char **argv) -{ - if (argc < 3) { - ErrorStr = copyright; - ErrorStr += usage; - return false; - } - int image_count = 0; - const char* output_file_name = NULL; - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "-fov") == 0) { - if (++i < argc) { - FieldOfView = (float) atof(argv[i]); - } - } else if (strcmp(argv[i], "-verbose") == 0) { - Verbose = true; - } else if (strcmp(argv[i], "-threshold") == 0) { - if (++i < argc) { - ThresholdPixels = atoi(argv[i]); - } - } else if (strcmp(argv[i], "-gamma") == 0) { - if (++i < argc) { - Gamma = (float) atof(argv[i]); - } - } else if (strcmp(argv[i], "-luminance") == 0) { - if (++i < argc) { - Luminance = (float) atof(argv[i]); - } - } else if (strcmp(argv[i], "-luminanceonly") == 0) { - LuminanceOnly = true; - } else if (strcmp(argv[i], "-colorfactor") == 0) { - if (++i < argc) { - ColorFactor = (float) atof(argv[i]); - } - } else if (strcmp(argv[i], "-downsample") == 0) { - if (++i < argc) { - DownSample = (int) atoi(argv[i]); - } - } else if (strcmp(argv[i], "-output") == 0) { - if (++i < argc) { - output_file_name = argv[i]; - } - } else if (image_count < 2) { - RGBAImage* img = RGBAImage::ReadFromFile(argv[i]); - if (!img) { - ErrorStr = "FAIL: Cannot open "; - ErrorStr += argv[i]; - ErrorStr += "\n"; - return false; - } else { - ++image_count; - if(image_count == 1) - ImgA = img; - else - ImgB = img; - } - } else { - fprintf(stderr, "Warning: option/file \"%s\" ignored\n", argv[i]); - } - } // i - if(!ImgA || !ImgB) { - ErrorStr = "FAIL: Not enough image files specified\n"; - return false; - } - for (int i = 0; i < DownSample; i++) { - if (Verbose) printf("Downsampling by %d\n", 1 << (i+1)); - RGBAImage *tmp = ImgA->DownSample(); - if (tmp) { - delete ImgA; - ImgA = tmp; - } - tmp = ImgB->DownSample(); - if (tmp) { - delete ImgB; - ImgB = tmp; - } - } - if(output_file_name) { - ImgDiff = new RGBAImage(ImgA->Get_Width(), ImgA->Get_Height(), output_file_name); - } - return true; -} - -void CompareArgs::Print_Args() -{ - printf("Field of view is %f degrees\n", FieldOfView); - printf("Threshold pixels is %d pixels\n", ThresholdPixels); - printf("The Gamma is %f\n", Gamma); - printf("The Display's luminance is %f candela per meter squared\n", Luminance); -} - -////////////////////////////////////////////////////////////////////// -// Construction/Destruction -////////////////////////////////////////////////////////////////////// - -LPyramid::LPyramid(float *image, int width, int height) : - Width(width), - Height(height) -{ - // Make the Laplacian pyramid by successively - // copying the earlier levels and blurring them - for (int i=0; i<MAX_PYR_LEVELS; i++) { - if (i == 0) { - Levels[i] = Copy(image); - } else { - Levels[i] = new float[Width * Height]; - Convolve(Levels[i], Levels[i - 1]); - } - } -} - -LPyramid::~LPyramid() -{ - for (int i=0; i<MAX_PYR_LEVELS; i++) { - if (Levels[i]) delete Levels[i]; - } -} - -float *LPyramid::Copy(float *img) -{ - int max = Width * Height; - float *out = new float[max]; - for (int i = 0; i < max; i++) out[i] = img[i]; - - return out; -} - -void LPyramid::Convolve(float *a, float *b) -// convolves image b with the filter kernel and stores it in a -{ - int y,x,i,j,nx,ny; - const float Kernel[] = {0.05f, 0.25f, 0.4f, 0.25f, 0.05f}; - - for (y=0; y<Height; y++) { - for (x=0; x<Width; x++) { - int index = y * Width + x; - a[index] = 0.0f; - for (i=-2; i<=2; i++) { - for (j=-2; j<=2; j++) { - nx=x+i; - ny=y+j; - if (nx<0) nx=-nx; - if (ny<0) ny=-ny; - if (nx>=Width) nx=2*Width-nx-1; - if (ny>=Height) ny=2*Height-ny-1; - a[index] += Kernel[i+2] * Kernel[j+2] * b[ny * Width + nx]; - } - } - } - } -} - -float LPyramid::Get_Value(int x, int y, int level) -{ - int index = x + y * Width; - int l = level; - if (l > MAX_PYR_LEVELS) l = MAX_PYR_LEVELS; - return Levels[level][index]; -} - - - -#ifndef M_PI -#define M_PI 3.14159265f -#endif - -/* -* Given the adaptation luminance, this function returns the -* threshold of visibility in cd per m^2 -* TVI means Threshold vs Intensity function -* This version comes from Ward Larson Siggraph 1997 -*/ - -float tvi(float adaptation_luminance) -{ - // returns the threshold luminance given the adaptation luminance - // units are candelas per meter squared - - float log_a, r, result; - log_a = log10f(adaptation_luminance); - - if (log_a < -3.94f) { - r = -2.86f; - } else if (log_a < -1.44f) { - r = powf(0.405f * log_a + 1.6f , 2.18f) - 2.86f; - } else if (log_a < -0.0184f) { - r = log_a - 0.395f; - } else if (log_a < 1.9f) { - r = powf(0.249f * log_a + 0.65f, 2.7f) - 0.72f; - } else { - r = log_a - 1.255f; - } - - result = powf(10.0f , r); - - return result; - -} - -// computes the contrast sensitivity function (Barten SPIE 1989) -// given the cycles per degree (cpd) and luminance (lum) -float csf(float cpd, float lum) -{ - float a, b, result; - - a = 440.0f * powf((1.0f + 0.7f / lum), -0.2f); - b = 0.3f * powf((1.0f + 100.0f / lum), 0.15f); - - result = a * cpd * expf(-b * cpd) * sqrtf(1.0f + 0.06f * expf(b * cpd)); - - return result; -} - -/* -* Visual Masking Function -* from Daly 1993 -*/ -float mask(float contrast) -{ - float a, b, result; - a = powf(392.498f * contrast, 0.7f); - b = powf(0.0153f * a, 4.0f); - result = powf(1.0f + b, 0.25f); - - return result; -} - -// convert Adobe RGB (1998) with reference white D65 to XYZ -void AdobeRGBToXYZ(float r, float g, float b, float &x, float &y, float &z) -{ - // matrix is from http://www.brucelindbloom.com/ - x = r * 0.576700f + g * 0.185556f + b * 0.188212f; - y = r * 0.297361f + g * 0.627355f + b * 0.0752847f; - z = r * 0.0270328f + g * 0.0706879f + b * 0.991248f; -} - -void XYZToLAB(float x, float y, float z, float &L, float &A, float &B) -{ - static float xw = -1; - static float yw; - static float zw; - // reference white - if (xw < 0) { - AdobeRGBToXYZ(1, 1, 1, xw, yw, zw); - } - const float epsilon = 216.0f / 24389.0f; - const float kappa = 24389.0f / 27.0f; - float f[3]; - float r[3]; - r[0] = x / xw; - r[1] = y / yw; - r[2] = z / zw; - for (int i = 0; i < 3; i++) { - if (r[i] > epsilon) { - f[i] = powf(r[i], 1.0f / 3.0f); - } else { - f[i] = (kappa * r[i] + 16.0f) / 116.0f; - } - } - L = 116.0f * f[1] - 16.0f; - A = 500.0f * (f[0] - f[1]); - B = 200.0f * (f[1] - f[2]); -} - -bool Yee_Compare(CompareArgs &args) -{ - if ((args.ImgA->Get_Width() != args.ImgB->Get_Width()) || - (args.ImgA->Get_Height() != args.ImgB->Get_Height())) { - args.ErrorStr = "Image dimensions do not match\n"; - return false; - } - - unsigned int i, dim; - dim = args.ImgA->Get_Width() * args.ImgA->Get_Height(); - bool identical = true; - for (i = 0; i < dim; i++) { - if (args.ImgA->Get(i) != args.ImgB->Get(i)) { - identical = false; - break; - } - } - if (identical) { - args.ErrorStr = "Images are binary identical\n"; - return true; - } - - // assuming colorspaces are in Adobe RGB (1998) convert to XYZ - float *aX = new float[dim]; - float *aY = new float[dim]; - float *aZ = new float[dim]; - float *bX = new float[dim]; - float *bY = new float[dim]; - float *bZ = new float[dim]; - float *aLum = new float[dim]; - float *bLum = new float[dim]; - - float *aA = new float[dim]; - float *bA = new float[dim]; - float *aB = new float[dim]; - float *bB = new float[dim]; - - if (args.Verbose) printf("Converting RGB to XYZ\n"); - - unsigned int x, y, w, h; - w = args.ImgA->Get_Width(); - h = args.ImgA->Get_Height(); - for (y = 0; y < h; y++) { - for (x = 0; x < w; x++) { - float r, g, b, l; - i = x + y * w; - r = powf(args.ImgA->Get_Red(i) / 255.0f, args.Gamma); - g = powf(args.ImgA->Get_Green(i) / 255.0f, args.Gamma); - b = powf(args.ImgA->Get_Blue(i) / 255.0f, args.Gamma); - AdobeRGBToXYZ(r,g,b,aX[i],aY[i],aZ[i]); - XYZToLAB(aX[i], aY[i], aZ[i], l, aA[i], aB[i]); - r = powf(args.ImgB->Get_Red(i) / 255.0f, args.Gamma); - g = powf(args.ImgB->Get_Green(i) / 255.0f, args.Gamma); - b = powf(args.ImgB->Get_Blue(i) / 255.0f, args.Gamma); - AdobeRGBToXYZ(r,g,b,bX[i],bY[i],bZ[i]); - XYZToLAB(bX[i], bY[i], bZ[i], l, bA[i], bB[i]); - aLum[i] = aY[i] * args.Luminance; - bLum[i] = bY[i] * args.Luminance; - } - } - - if (args.Verbose) printf("Constructing Laplacian Pyramids\n"); - - LPyramid *la = new LPyramid(aLum, w, h); - LPyramid *lb = new LPyramid(bLum, w, h); - - float num_one_degree_pixels = (float) (2 * tan( args.FieldOfView * 0.5 * M_PI / 180) * 180 / M_PI); - float pixels_per_degree = w / num_one_degree_pixels; - - if (args.Verbose) printf("Performing test\n"); - - float num_pixels = 1; - unsigned int adaptation_level = 0; - for (i = 0; i < MAX_PYR_LEVELS; i++) { - adaptation_level = i; - if (num_pixels > num_one_degree_pixels) break; - num_pixels *= 2; - } - - float cpd[MAX_PYR_LEVELS]; - cpd[0] = 0.5f * pixels_per_degree; - for (i = 1; i < MAX_PYR_LEVELS; i++) cpd[i] = 0.5f * cpd[i - 1]; - float csf_max = csf(3.248f, 100.0f); - - float F_freq[MAX_PYR_LEVELS - 2]; - for (i = 0; i < MAX_PYR_LEVELS - 2; i++) F_freq[i] = csf_max / csf( cpd[i], 100.0f); - - unsigned int pixels_failed = 0; - for (y = 0; y < h; y++) { - for (x = 0; x < w; x++) { - int index = x + y * w; - float contrast[MAX_PYR_LEVELS - 2]; - float sum_contrast = 0; - for (i = 0; i < MAX_PYR_LEVELS - 2; i++) { - float n1 = fabsf(la->Get_Value(x,y,i) - la->Get_Value(x,y,i + 1)); - float n2 = fabsf(lb->Get_Value(x,y,i) - lb->Get_Value(x,y,i + 1)); - float numerator = (n1 > n2) ? n1 : n2; - float d1 = fabsf(la->Get_Value(x,y,i+2)); - float d2 = fabsf(lb->Get_Value(x,y,i+2)); - float denominator = (d1 > d2) ? d1 : d2; - if (denominator < 1e-5f) denominator = 1e-5f; - contrast[i] = numerator / denominator; - sum_contrast += contrast[i]; - } - if (sum_contrast < 1e-5) sum_contrast = 1e-5f; - float F_mask[MAX_PYR_LEVELS - 2]; - float adapt = la->Get_Value(x,y,adaptation_level) + lb->Get_Value(x,y,adaptation_level); - adapt *= 0.5f; - if (adapt < 1e-5) adapt = 1e-5f; - for (i = 0; i < MAX_PYR_LEVELS - 2; i++) { - F_mask[i] = mask(contrast[i] * csf(cpd[i], adapt)); - } - float factor = 0; - for (i = 0; i < MAX_PYR_LEVELS - 2; i++) { - factor += contrast[i] * F_freq[i] * F_mask[i] / sum_contrast; - } - if (factor < 1) factor = 1; - if (factor > 10) factor = 10; - float delta = fabsf(la->Get_Value(x,y,0) - lb->Get_Value(x,y,0)); - bool pass = true; - // pure luminance test - if (delta > factor * tvi(adapt)) { - pass = false; - } else if (!args.LuminanceOnly) { - // CIE delta E test with modifications - float color_scale = args.ColorFactor; - // ramp down the color test in scotopic regions - if (adapt < 10.0f) { - // Don't do color test at all. - color_scale = 0.0; - } - float da = aA[index] - bA[index]; - float db = aB[index] - bB[index]; - da = da * da; - db = db * db; - float delta_e = (da + db) * color_scale; - if (delta_e > factor) { - pass = false; - } - } - if (!pass) { - pixels_failed++; - if (args.ImgDiff) { - args.ImgDiff->Set(255, 0, 0, 255, index); - } - } else { - if (args.ImgDiff) { - args.ImgDiff->Set(0, 0, 0, 255, index); - } - } - } - } - - if (aX) delete[] aX; - if (aY) delete[] aY; - if (aZ) delete[] aZ; - if (bX) delete[] bX; - if (bY) delete[] bY; - if (bZ) delete[] bZ; - if (aLum) delete[] aLum; - if (bLum) delete[] bLum; - if (la) delete la; - if (lb) delete lb; - if (aA) delete aA; - if (bA) delete bA; - if (aB) delete aB; - if (bB) delete bB; - - char different[100]; - sprintf(different, "%d pixels are different\n", pixels_failed); - - // Always output image difference if requested. - if (args.ImgDiff) { - if (args.ImgDiff->WriteToFile(args.ImgDiff->Get_Name().c_str())) { - args.ErrorStr += "Wrote difference image to "; - args.ErrorStr+= args.ImgDiff->Get_Name(); - args.ErrorStr += "\n"; - } else { - args.ErrorStr += "Could not write difference image to "; - args.ErrorStr+= args.ImgDiff->Get_Name(); - args.ErrorStr += "\n"; - } - } - - if (pixels_failed < args.ThresholdPixels) { - args.ErrorStr = "Images are perceptually indistinguishable\n"; - args.ErrorStr += different; - return true; - } - - args.ErrorStr = "Images are visibly different\n"; - args.ErrorStr += different; - - return false; -} - -RGBAImage* RGBAImage::DownSample() const { - if (Width <=1 || Height <=1) return NULL; - int nw = Width / 2; - int nh = Height / 2; - RGBAImage* img = new RGBAImage(nw, nh, Name.c_str()); - for (int y = 0; y < nh; y++) { - for (int x = 0; x < nw; x++) { - int d[4]; - // Sample a 2x2 patch from the parent image. - d[0] = Get(2 * x + 0, 2 * y + 0); - d[1] = Get(2 * x + 1, 2 * y + 0); - d[2] = Get(2 * x + 0, 2 * y + 1); - d[3] = Get(2 * x + 1, 2 * y + 1); - int rgba = 0; - // Find the average color. - for (int i = 0; i < 4; i++) { - int c = (d[0] >> (8 * i)) & 0xFF; - c += (d[1] >> (8 * i)) & 0xFF; - c += (d[2] >> (8 * i)) & 0xFF; - c += (d[3] >> (8 * i)) & 0xFF; - c /= 4; - rgba |= (c & 0xFF) << (8 * i); - } - img->Set(x, y, rgba); - } - } - return img; -} - - -bool RGBAImage::WriteToFile(const char* filename) -{ - LodePNG::Encoder encoder; - encoder.addText("Comment","lodepng"); - encoder.getSettings().zlibsettings.windowSize = 2048; - - -/* - const FREE_IMAGE_FORMAT fileType = FreeImage_GetFIFFromFilename(filename); - if(FIF_UNKNOWN == fileType) - { - printf("Can't save to unknown filetype %s\n", filename); - return false; - } - - FIBITMAP* bitmap = FreeImage_Allocate(Width, Height, 32, 0x000000ff, 0x0000ff00, 0x00ff0000); - if(!bitmap) - { - printf("Failed to create freeimage for %s\n", filename); - return false; - } - - const unsigned int* source = Data; - for( int y=0; y < Height; y++, source += Width ) - { - unsigned int* scanline = (unsigned int*)FreeImage_GetScanLine(bitmap, Height - y - 1 ); - memcpy(scanline, source, sizeof(source[0]) * Width); - } - - FreeImage_SetTransparent(bitmap, false); - FIBITMAP* converted = FreeImage_ConvertTo24Bits(bitmap); - - - const bool result = !!FreeImage_Save(fileType, converted, filename); - if(!result) - printf("Failed to save to %s\n", filename); - - FreeImage_Unload(converted); - FreeImage_Unload(bitmap); - return result; -*/ - return true; -} - -RGBAImage* RGBAImage::ReadFromFile(const char* filename) -{ - unsigned char* buffer; - unsigned char* image; - size_t buffersize, imagesize, i; - LodePNG_Decoder decoder; - - LodePNG_loadFile(&buffer, &buffersize, filename); /*load the image file with given filename*/ - LodePNG_Decoder_init(&decoder); - LodePNG_Decoder_decode(&decoder, &image, &imagesize, buffer, buffersize); /*decode the png*/ - - /*load and decode*/ - /*if there's an error, display it, otherwise display information about the image*/ - if(decoder.error) printf("error %u: %s\n", decoder.error, LodePNG_error_text(decoder.error)); - - int w = decoder.infoPng.width; - int h = decoder.infoPng.height; - - - RGBAImage* result = new RGBAImage(w, h, filename); - // Copy the image over to our internal format, FreeImage has the scanlines bottom to top though. - unsigned int* dest = result->Data; - memcpy(dest, (void *)image, h*w*4); - - /*cleanup decoder*/ - free(image); - free(buffer); - LodePNG_Decoder_cleanup(&decoder); - - return result; -/* - const FREE_IMAGE_FORMAT fileType = FreeImage_GetFileType(filename); - if(FIF_UNKNOWN == fileType) - { - printf("Unknown filetype %s\n", filename); - return 0; - } - - FIBITMAP* freeImage = 0; - if(FIBITMAP* temporary = FreeImage_Load(fileType, filename, 0)) - { - freeImage = FreeImage_ConvertTo32Bits(temporary); - FreeImage_Unload(temporary); - } - if(!freeImage) - { - printf( "Failed to load the image %s\n", filename); - return 0; - } - - const int w = FreeImage_GetWidth(freeImage); - const int h = FreeImage_GetHeight(freeImage); - - RGBAImage* result = new RGBAImage(w, h, filename); - // Copy the image over to our internal format, FreeImage has the scanlines bottom to top though. - unsigned int* dest = result->Data; - for( int y=0; y < h; y++, dest += w ) - { - const unsigned int* scanline = (const unsigned int*)FreeImage_GetScanLine(freeImage, h - y - 1 ); - memcpy(dest, scanline, sizeof(dest[0]) * w); - } - - FreeImage_Unload(freeImage); - return result; - return NULL; -*/ -} - - -int main(int argc, char **argv) -{ - CompareArgs args; - - if (!args.Parse_Args(argc, argv)) { - printf("%s", args.ErrorStr.c_str()); - return -1; - } else { - if (args.Verbose) args.Print_Args(); - } - - const bool passed = Yee_Compare(args); - if (passed) { - if(args.Verbose) - printf("PASS: %s\n", args.ErrorStr.c_str()); - } else { - printf("FAIL: %s\n", args.ErrorStr.c_str()); - } - - return passed ? 0 : 1; -} - diff --git a/tests/yee_compare.h b/tests/yee_compare.h deleted file mode 100644 index 041ae4c..0000000 --- a/tests/yee_compare.h +++ /dev/null @@ -1,126 +0,0 @@ -#ifndef _yee_compare_h -#define _yee_compare_h - -// source code modified for OpenSCAD, Sept 2011 -// original copyright notice follows: -/* -Metric -RGBAImage.h -Comapre Args -Laplacian Pyramid -Copyright (C) 2006 Yangli Hector Yee - -This program is free software; you can redistribute it and/or modify it under the terms of the -GNU General Public License as published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along with this program; -if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include <string> - -class RGBAImage; - -// Args to pass into the comparison function -class CompareArgs -{ -public: - CompareArgs(); - ~CompareArgs(); - bool Parse_Args(int argc, char **argv); - void Print_Args(); - - RGBAImage *ImgA; // Image A - RGBAImage *ImgB; // Image B - RGBAImage *ImgDiff; // Diff image - bool Verbose; // Print lots of text or not - bool LuminanceOnly; // Only consider luminance; ignore chroma channels in the comparison. - float FieldOfView; // Field of view in degrees - float Gamma; // The gamma to convert to linear color space - float Luminance; // the display's luminance - unsigned int ThresholdPixels; // How many pixels different to ignore - std::string ErrorStr; // Error string - // How much color to use in the metric. - // 0.0 is the same as LuminanceOnly = true, - // 1.0 means full strength. - float ColorFactor; - // How much to down sample image before comparing, in powers of 2. - int DownSample; -}; - -#define MAX_PYR_LEVELS 8 - -class LPyramid -{ -public: - LPyramid(float *image, int width, int height); - virtual ~LPyramid(); - float Get_Value(int x, int y, int level); -protected: - float *Copy(float *img); - void Convolve(float *a, float *b); - - // Succesively blurred versions of the original image - float *Levels[MAX_PYR_LEVELS]; - - int Width; - int Height; -}; - -class CompareArgs; - -// Image comparison metric using Yee's method -// References: A Perceptual Metric for Production Testing, Hector Yee, Journal of Graphics Tools 2004 -bool Yee_Compare(CompareArgs &args); - -/** Class encapsulating an image containing R,G,B,A channels. - * - * Internal representation assumes data is in the ABGR format, with the RGB - * color channels premultiplied by the alpha value. Premultiplied alpha is - * often also called "associated alpha" - see the tiff 6 specification for some - * discussion - http://partners.adobe.com/asn/developer/PDFS/TN/TIFF6.pdf - * - */ -class RGBAImage -{ - RGBAImage(const RGBAImage&); - RGBAImage& operator=(const RGBAImage&); -public: - RGBAImage(int w, int h, const char *name = 0) - { - Width = w; - Height = h; - if (name) Name = name; - Data = new unsigned int[w * h]; - }; - ~RGBAImage() { if (Data) delete[] Data; } - unsigned char Get_Red(unsigned int i) { return (Data[i] & 0xFF); } - unsigned char Get_Green(unsigned int i) { return ((Data[i]>>8) & 0xFF); } - unsigned char Get_Blue(unsigned int i) { return ((Data[i]>>16) & 0xFF); } - unsigned char Get_Alpha(unsigned int i) { return ((Data[i]>>24) & 0xFF); } - void Set(unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned int i) - { Data[i] = r | (g << 8) | (b << 16) | (a << 24); } - int Get_Width(void) const { return Width; } - int Get_Height(void) const { return Height; } - void Set(int x, int y, unsigned int d) { Data[x + y * Width] = d; } - unsigned int Get(int x, int y) const { return Data[x + y * Width]; } - unsigned int Get(int i) const { return Data[i]; } - const std::string &Get_Name(void) const { return Name; } - RGBAImage* DownSample() const; - - bool WriteToFile(const char* filename); - static RGBAImage* ReadFromFile(const char* filename); - -protected: - int Width; - int Height; - std::string Name; - unsigned int *Data; -}; - -#endif |