Merge branch 'cakebaby' of github.com:donbright/openscad into cakebaby

Conflicts:
	tests/CMakeLists.txt
stl_dim
Don Bright 2011-11-26 12:17:54 -06:00
commit 8197294d4c
24 changed files with 3513 additions and 900 deletions

View File

@ -71,6 +71,7 @@ o 3D View
- Use OpenGL picking to facilitate ray-tracing like features like measuring
thicknesses, distances, slot thicknesses etc.
- Add option to change lights, e.g. add an optional camera light
- 2D objects are rendered at z = -0.1 - why?
o Editor wishlist
- More infrastructure for external editor (allow communication from the outside)
- Preferences GUI for the features below

@ -1 +1 @@
Subproject commit 4330afe5e726b910e0b60039c86afa809f949239
Subproject commit b7d9ec4c5e5939b2bffcda60b91f4623e9b3c625

View File

@ -11,6 +11,11 @@ using boost::intmax_t;
using boost::uintmax_t;
#endif
#include <CGAL/version.h>
#if CGAL_VERSION_NR < 1030601000
#error CGAL >= 3.6 is required!
#endif
#include <CGAL/Gmpq.h>
#include <CGAL/Extended_cartesian.h>
#include <CGAL/Nef_polyhedron_2.h>

View File

@ -3,6 +3,7 @@
#include "node.h"
#include "state.h"
#include <algorithm>
#include <boost/foreach.hpp>
void Traverser::execute()
{
@ -10,35 +11,37 @@ void Traverser::execute()
traverse(this->root, state);
}
struct TraverseNode
Response Traverser::traverse(const AbstractNode &node, const State &state)
{
Traverser *traverser;
const State &state;
TraverseNode(Traverser *traverser, const State &state) :
traverser(traverser), state(state) {}
void operator()(const AbstractNode *node) { traverser->traverse(*node, state); }
};
void Traverser::traverse(const AbstractNode &node, const State &state)
{
// FIXME: Handle abort
State newstate = state;
newstate.setNumChildren(node.getChildren().size());
Response response;
if (traversaltype == PREFIX || traversaltype == PRE_AND_POSTFIX) {
newstate.setPrefix(true);
newstate.setParent(state.parent());
node.accept(newstate, this->visitor);
response = node.accept(newstate, this->visitor);
}
newstate.setParent(&node);
std::for_each(node.getChildren().begin(), node.getChildren().end(), TraverseNode(this, newstate));
if (traversaltype == POSTFIX || traversaltype == PRE_AND_POSTFIX) {
newstate.setParent(state.parent());
newstate.setPrefix(false);
newstate.setPostfix(true);
node.accept(newstate, this->visitor);
// Pruned traversals mean don't traverse children
if (response == ContinueTraversal) {
newstate.setParent(&node);
BOOST_FOREACH(const AbstractNode *chnode, node.getChildren()) {
response = this->traverse(*chnode, newstate);
if (response == AbortTraversal) return response; // Abort immediately
}
}
// Postfix is executed for all non-aborted traversals
if (response != AbortTraversal) {
if (traversaltype == POSTFIX || traversaltype == PRE_AND_POSTFIX) {
newstate.setParent(state.parent());
newstate.setPrefix(false);
newstate.setPostfix(true);
response = node.accept(newstate, this->visitor);
}
}
if (response != AbortTraversal) response = ContinueTraversal;
return response;
}

View File

@ -15,7 +15,7 @@ public:
void execute();
// FIXME: reverse parameters
void traverse(const AbstractNode &node, const class State &state);
Response traverse(const AbstractNode &node, const class State &state);
private:
Visitor &visitor;

View File

@ -363,7 +363,20 @@ std::string Value::toString() const
<< ']';
break;
case NUMBER:
#ifdef OPENSCAD_TESTING
// Quick and dirty hack to work around floating point rounding differences
// across platforms for testing purposes.
{
std::stringstream tmp;
tmp.precision(16);
tmp << this->num;
std::string tmpstr = tmp.str();
if (tmpstr.size() > 16) tmpstr.erase(16);
stream << tmpstr;
}
#else
stream << this->num;
#endif
break;
case BOOL:
stream << (this->b ? "true" : "false");

22
testdata/scad/bugs/fn_bug.scad vendored Normal file
View File

@ -0,0 +1,22 @@
fn_setting = 41; // does not work
//fn_setting = 40; // works
// basic box sizes
box_width = 720;
box_depth = 450;
box_height = 90;
box_bevel = 35;
union ()
{
translate ([(box_width / 2) - box_bevel, -((box_depth / 2) - box_bevel), 0])
cylinder (h = box_height- box_bevel, r = box_bevel, center = false, $fn = fn_setting);
translate ([0, -((box_depth / 2) - box_bevel), box_height- box_bevel])
rotate ([0, 90, 0])
cylinder (h = box_width - (2 * box_bevel), r = box_bevel, center = true, $fn = fn_setting);
translate ([(box_width / 2) - box_bevel, 0, box_height- box_bevel])
rotate ([90, 90, 0])
cylinder (h = box_depth - (2 * box_bevel), r = box_bevel, center = true, $fn = fn_setting);
}

3320
testdata/scad/bugs/fn_bug.stl vendored Normal file

File diff suppressed because it is too large Load Diff

14
testdata/scad/bugs/fn_bug.txt vendored Normal file
View File

@ -0,0 +1,14 @@
Reported by Ian Shumsky:
1) Export STL from fn_bug.scad
- fn_setting = 41 -> triggers bug
- fn_setting = 40 -> works
2) Open fn_bug_import.scad and render using CGAL (F6)
->
CGAL error in CGA_Nef_polyhedron3(): CGAL ERROR: assertion violation!
Expr: pe_prev->is_border() || !internal::Plane_constructor<Plane>::get_plane(pe_prev->facet(),pe_prev->facet()->plane()).is_degenerate()
File: ../libraries/install/include/CGAL/Nef_3/polyhedron_3_to_nef_3.h
Line: 252

1
testdata/scad/bugs/fn_bug_import.scad vendored Normal file
View File

@ -0,0 +1 @@
import("fn_bug.stl");

View File

@ -13,5 +13,6 @@ translate([2,-2,0]) polygon([[0,0], [1,0], [0.8,0.5], [1,1], [0,1]]);
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]];
translate([-2,0,0]) polygon(points);
translate([-2,-2,0]) polygon(points=points, paths=[[0,1,2,3], [4,5,6,7]]);
translate([2,-4,0]) polygon([[0,0], [1,0], [1,1], [0,0]]);
// FIXME: convexity

View File

@ -1 +1 @@
import(file="A-\\ B-\" C-\t D-\n E-' F-\\\\", layer="A:\\ B:\" C:\t D:\n E:' F:\\\\");
import(file="B-\" C-\t D-\n E-'", layer="A:\\ B:\" C:\t D:\n E:' F:\\\\");

View File

@ -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
@ -293,12 +310,6 @@ add_library(tests-offscreen STATIC ${OFFSCREEN_SOURCES})
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
#
@ -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()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -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);

View File

@ -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);
}

View File

@ -1 +1 @@
ECHO: 3.141592653589793
ECHO: 3.14159265358979

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@ -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:

View File

@ -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()

View File

@ -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;
}

View File

@ -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