mirror of https://github.com/vitalif/openscad
added png and image comparison, enabling testing on linux
parent
78bf15d085
commit
6d70855a4d
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
280
src/polyset.cc
280
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<CGAL_Nef_polyhedron2::Point> 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<CGAL_Nef_polyhedron2::Point> 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<CGAL_Nef_polyhedron2::Point> grid(GRID_COARSE);
|
||||
|
||||
for (int i = 0; i < this->polygons.size(); i++) {
|
||||
std::list<CGAL_Nef_polyhedron2::Point> 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<int> grid;
|
||||
QHash< QPair<int,int>, QPair<int,int> > egde_to_poly;
|
||||
QHash< int, CGAL_Nef_polyhedron2::Point > points;
|
||||
QHash< int, QList<int> > 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<int,int>(a, b)].first == 0)
|
||||
this->egde_to_poly[QPair<int,int>(a, b)].first = pn;
|
||||
else if (this->egde_to_poly[QPair<int,int>(a, b)].second == 0)
|
||||
this->egde_to_poly[QPair<int,int>(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<int,int>(a, b)].first == pn)
|
||||
this->egde_to_poly[QPair<int,int>(a, b)].first = 0;
|
||||
if (this->egde_to_poly[QPair<int,int>(a, b)].second == pn)
|
||||
this->egde_to_poly[QPair<int,int>(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<int> work_queue;
|
||||
QHashIterator< int, QList<int> > 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<int,int>(a, b)].first != 0 &&
|
||||
this->egde_to_poly[QPair<int,int>(a, b)].second != 0) {
|
||||
int poly2_n = this->egde_to_poly[QPair<int,int>(a, b)].first +
|
||||
this->egde_to_poly[QPair<int,int>(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<int,int>(c, d)].first +
|
||||
this->egde_to_poly[QPair<int,int>(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<int> > it(polygons);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
std::list<CGAL_Nef_polyhedron2::Point> 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<CGAL_Nef_polyhedron2::Point> grid(GRID_COARSE);
|
||||
|
||||
for (int i = 0; i < this->borders.size(); i++) {
|
||||
std::list<CGAL_Nef_polyhedron2::Point> 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
|
||||
}
|
||||
|
|
|
@ -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<std::string> argnames;
|
||||
std::vector<Expression*> 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<AbstractNode *> 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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;y<height;y++) {
|
||||
for (int x=0;x<width;x++) {
|
||||
int offs1 = y*width*pixel_size + x*pixel_size;
|
||||
int offs2 = (height-1-y)*width*pixel_size + x*pixel_size;
|
||||
pixels_flipped[offs1 ] = pixels[offs2 ];
|
||||
pixels_flipped[offs1+1] = pixels[offs2+1];
|
||||
pixels_flipped[offs1+2] = pixels[offs2+2];
|
||||
pixels_flipped[offs1+3] = pixels[offs2+3];
|
||||
}
|
||||
}
|
||||
//encoder.settings.zlibsettings.windowSize = 2048;
|
||||
//LodePNG_Text_add(&encoder.infoPng.text, "Comment", "Created with LodePNG");
|
||||
|
||||
LodePNG_encode(&dataout, &dataout_size, pixels_flipped, width, height, LCT_RGBA, 8);
|
||||
//LodePNG_saveFile(dataout, dataout_size, "blah2.png");
|
||||
FILE *f = fopen( filename, "w" );
|
||||
if (f) {
|
||||
fwrite( dataout, 1, dataout_size, f);
|
||||
fclose(f);
|
||||
}
|
||||
free(pixels_flipped);
|
||||
free(dataout);
|
||||
}
|
||||
|
||||
OffscreenContext *create_offscreen_context(int w, int h)
|
||||
{
|
||||
|
@ -34,11 +82,11 @@ OffscreenContext *create_offscreen_context(int w, int h)
|
|||
|
||||
// dummy window
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
SDL_SetVideoMode(256,256,32,SDL_OPENGL);
|
||||
SDL_SetVideoMode(ctx->width,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;
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -144,17 +144,11 @@ int main(int argc, char **argv)
|
|||
|
||||
// cout << tree.getString(*root_node) << "\n";
|
||||
|
||||
<<<<<<< HEAD
|
||||
CSGTermEvaluator evaluator(tree);
|
||||
vector<CSGTerm*> empty = vector<CSGTerm*>();
|
||||
CSGTerm *root_term = evaluator.evaluateCSGTerm(*root_node, empty, empty);
|
||||
=======
|
||||
vector<CSGTerm*> highlights;
|
||||
vector<CSGTerm*> 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<int, class CSGTerm*>::iterator iter = evaluator.stored_term.begin();
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
||||
|
|
|
@ -0,0 +1,680 @@
|
|||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -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 <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
|
||||
|
Loading…
Reference in New Issue