From d83560835237d360f6d12776435e91676c8ab45f Mon Sep 17 00:00:00 2001 From: Sam Moore Date: Sun, 24 Aug 2014 18:30:32 +0800 Subject: [PATCH] Support colours in shading Shading doesn't actually work still but that's of minor concern --- src/controlpanel.cpp | 27 +++++++++ src/controlpanel.h | 4 ++ src/document.cpp | 125 ++++++++++++++++++++++++++++++++++------- src/document.h | 15 ++--- src/ipdf.h | 10 ++-- src/objectrenderer.cpp | 46 +++++++++------ src/view.cpp | 5 +- src/view.h | 14 ++++- 8 files changed, 192 insertions(+), 54 deletions(-) diff --git a/src/controlpanel.cpp b/src/controlpanel.cpp index f2d0324..5b62e91 100644 --- a/src/controlpanel.cpp +++ b/src/controlpanel.cpp @@ -95,6 +95,16 @@ QMenu * ControlPanel::CreateViewMenu() view->addAction(m_view_set_bounds); connect(m_view_set_bounds, SIGNAL(triggered()), this, SLOT(SetViewBounds())); + m_view_show_object_bounds = new QAction("&Show Object Bounds", this); + m_view_show_object_bounds->setCheckable(true); + view->addAction(m_view_show_object_bounds); + connect(m_view_show_object_bounds, SIGNAL(triggered()), this, SLOT(ToggleShowObjectBounds())); + + m_view_enable_shading = new QAction("&Enable Shading", this); + m_view_enable_shading->setCheckable(true); + view->addAction(m_view_enable_shading); + connect(m_view_enable_shading, SIGNAL(triggered()), this, SLOT(ToggleEnableShading())); + return view; } @@ -147,6 +157,9 @@ void ControlPanel::UpdateAll() m_screen_cpu_rendering->setChecked(!using_gpu_rendering); m_screen_show_debug->setChecked(m_screen.DebugFontShown()); + m_view_show_object_bounds->setChecked(m_view.ShowingObjectBounds()); + m_view_enable_shading->setChecked(m_view.PerformingShading()); + // update things based on state const char * title; const char * tooltip; @@ -187,6 +200,20 @@ void ControlPanel::UpdateAll() setToolTip(tooltip); } +void ControlPanel::ToggleShowObjectBounds() +{ + bool state = m_view.ShowingObjectBounds(); + m_view.ShowObjectBounds(!state); + UpdateAll(); +} + +void ControlPanel::ToggleEnableShading() +{ + bool state = m_view.PerformingShading(); + m_view.PerformShading(!state); + UpdateAll(); +} + void ControlPanel::SetGPURendering() { m_view.SetGPURendering(true); diff --git a/src/controlpanel.h b/src/controlpanel.h index 90394f8..ea557d0 100644 --- a/src/controlpanel.h +++ b/src/controlpanel.h @@ -61,7 +61,9 @@ namespace IPDF private slots: void SetGPURendering(); void SetCPURendering(); + void ToggleShowObjectBounds(); void ToggleScreenDebugFont(); + void ToggleEnableShading(); void SetViewBounds(); void LoadSVGIntoDocument(); void SetDocumentFont(); @@ -105,6 +107,8 @@ namespace IPDF QAction * m_document_parse_svg; QAction * m_document_load_svg; QAction * m_view_set_bounds; + QAction * m_view_show_object_bounds; + QAction * m_view_enable_shading; QTextEdit * m_text_edit; diff --git a/src/document.cpp b/src/document.cpp index ead1b8e..8a5635c 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -133,19 +133,12 @@ int Document::ClipObjectToQuadChild(int object_id, QuadTreeNodeChildren type) case BEZIER: { Rect child_node_bounds = TransformFromQuadChild({0,0,1,1}, type); - Rect clip_bezier_bounds; - clip_bezier_bounds.x = (child_node_bounds.x - m_objects.bounds[object_id].x) / m_objects.bounds[object_id].w; - clip_bezier_bounds.y = (child_node_bounds.y - m_objects.bounds[object_id].y) / m_objects.bounds[object_id].h; - clip_bezier_bounds.w = child_node_bounds.w / m_objects.bounds[object_id].w; - clip_bezier_bounds.h = child_node_bounds.h / m_objects.bounds[object_id].h; - std::vector new_curves = m_objects.beziers[m_objects.data_indices[object_id]].ClipToRectangle(clip_bezier_bounds); + std::vector new_curves = m_objects.beziers[m_objects.data_indices[object_id]].ClipToRectangle(child_node_bounds); + Rect obj_bounds = TransformToQuadChild(m_objects.bounds[object_id], type); for (size_t i = 0; i < new_curves.size(); ++i) { - Rect new_bounds = TransformToQuadChild(m_objects.bounds[object_id], type); - //new_bounds = TransformToQuadChild(new_bounds, type); - //Bezier new_curve_data = new_curves[i].ToRelative(new_bounds); unsigned index = AddBezierData(new_curves[i]); - m_objects.bounds.push_back(new_bounds); + m_objects.bounds.push_back(obj_bounds); m_objects.types.push_back(BEZIER); m_objects.data_indices.push_back(index); } @@ -291,7 +284,7 @@ void Document::Load(const string & filename) #endif } -unsigned Document::AddGroup(unsigned start_index, unsigned end_index) +unsigned Document::AddGroup(unsigned start_index, unsigned end_index, const Colour & shading) { Real xmin = 0; Real ymin = 0; Real xmax = 0; Real ymax = 0; @@ -312,9 +305,11 @@ unsigned Document::AddGroup(unsigned start_index, unsigned end_index) } Rect bounds(xmin,ymin, xmax-xmin, ymax-ymin); - unsigned result = Add(GROUP, bounds,0); - m_objects.groups[m_count-1].first = start_index; - m_objects.groups[m_count-1].second = end_index; + + Group group = {start_index, end_index, shading}; + + unsigned data_index = AddGroupData(group); + unsigned result = Add(GROUP, bounds,data_index); return result; } @@ -334,7 +329,6 @@ unsigned Document::Add(ObjectType type, const Rect & bounds, unsigned data_index m_objects.types.push_back(type); m_objects.bounds.push_back(bounds); m_objects.data_indices.push_back(data_index); - m_objects.groups.push_back(pair(data_index, data_index)); return (m_count++); // Why can't we just use the size of types or something? } @@ -344,6 +338,11 @@ unsigned Document::AddBezierData(const Bezier & bezier) return m_objects.beziers.size()-1; } +unsigned Document::AddGroupData(const Group & group) +{ + m_objects.groups.push_back(group); + return m_objects.groups.size()-1; +} void Document::DebugDumpObjects() { @@ -401,6 +400,22 @@ static void GetXYPair(const string & d, Real & x, Real & y, unsigned & i,const s y = strtod(GetToken(d, token, i, delims).c_str(),NULL); } +static bool GetKeyValuePair(const string & d, string & key, string & value, unsigned & i, const string & delims = "()[],{}<>;:=") +{ + key = ""; + string token; + while (GetToken(d, token, i, delims) == ":" || token == ";"); + key = token; + if (GetToken(d, token, i, delims) != ":") + { + Error("Expected \":\" seperating key:value pair"); + return false; + } + value = ""; + GetToken(d, value, i, delims); + return true; +} + static void TransformXYPair(Real & x, Real & y, const SVGMatrix & transform) { Real x0(x); @@ -494,6 +509,8 @@ void Document::ParseSVGNode(pugi::xml_node & root, SVGMatrix & parent_transform) ParseSVGTransform(attrib_trans.as_string(), transform); } + + if (strcmp(child.name(), "svg") == 0 || strcmp(child.name(),"g") == 0 || strcmp(child.name(), "group") == 0) { @@ -504,9 +521,64 @@ void Document::ParseSVGNode(pugi::xml_node & root, SVGMatrix & parent_transform) else if (strcmp(child.name(), "path") == 0) { string d = child.attribute("d").as_string(); - Debug("Path data attribute is \"%s\"", d.c_str()); - pair range = ParseSVGPathData(d, transform); - AddGroup(range.first, range.second); + //Debug("Path data attribute is \"%s\"", d.c_str()); + bool closed = false; + pair range = ParseSVGPathData(d, transform, closed); + if (closed) + { + Colour c(0,0,0,0); + string colour_str(""); + map style; + if (child.attribute("style")) + { + ParseSVGStyleData(child.attribute("style").as_string(), style); + } + + // Determine shading colour + if (child.attribute("fill")) + { + colour_str = child.attribute("fill").as_string(); + } + else if (style.find("fill") != style.end()) + { + colour_str = style["fill"]; + } + if (colour_str == "red") + c = {1,0,0,1}; + else if (colour_str == "blue") + c = {0,0,1,1}; + else if (colour_str == "green") + c = {0,1,0,1}; + else if (colour_str == "black") + c = {0,0,0,1}; + else if (colour_str == "white") + c = {1,1,1,1}; + else if (colour_str.size() == 7 && colour_str[0] == '#') + { + Debug("Parse colour string: \"%s\"", colour_str.c_str()); + char comp[2] = {colour_str[1], colour_str[2]}; + c.r = Real(strtoul(comp, NULL, 16))/Real(255); + comp[0] = colour_str[3]; comp[1] = colour_str[4]; + c.g = Real(strtoul(comp, NULL, 16))/Real(255); + comp[0] = colour_str[5]; comp[1] = colour_str[6]; + c.b = Real(strtoul(comp, NULL, 16))/Real(255); + c.a = 1; + Debug("Colour is: %f, %f, %f, %f", Float(c.r), Float(c.g), Float(c.b), Float(c.a)); + } + + // Determin shading alpha + if (child.attribute("fill-opacity")) + { + c.a = child.attribute("fill-opacity").as_float(); + } + else if (style.find("fill-opacity") != style.end()) + { + c.a = strtod(style["fill-opacity"].c_str(), NULL); + } + + Debug("fill-opacity is %f", Float(c.a)); + AddGroup(range.first, range.second, c); + } } else if (strcmp(child.name(), "line") == 0) @@ -564,6 +636,17 @@ void Document::ParseSVGNode(pugi::xml_node & root, SVGMatrix & parent_transform) } } +void Document::ParseSVGStyleData(const string & style, map & results) +{ + unsigned i = 0; + string key; + string value; + while (i < style.size() && GetKeyValuePair(style, key, value, i)) + { + results[key] = value; + } +} + /** * Parse an SVG string into a rectangle */ @@ -608,8 +691,9 @@ void Document::LoadSVG(const string & filename, const Rect & bounds) // Fear the wrath of the tokenizing svg data // Seriously this isn't really very DOM-like at all is it? -pair Document::ParseSVGPathData(const string & d, const SVGMatrix & transform) +pair Document::ParseSVGPathData(const string & d, const SVGMatrix & transform, bool & closed) { + closed = false; Real x[4] = {0,0,0,0}; Real y[4] = {0,0,0,0}; @@ -709,7 +793,7 @@ pair Document::ParseSVGPathData(const string & d, const SVGM } else if (command == "l" || command == "L" || command == "h" || command == "H" || command == "v" || command == "V") { - Debug("Construct lineto command, relative %d", relative); + //Debug("Construct lineto command, relative %d", relative); Real dx = Real(strtod(GetToken(d,token,i,delims).c_str(),NULL)); Real dy; @@ -770,6 +854,7 @@ pair Document::ParseSVGPathData(const string & d, const SVGM x[0] = x3; y[0] = y3; command = "m"; + closed = true; } else { diff --git a/src/document.h b/src/document.h index 3ff607b..df818f2 100644 --- a/src/document.h +++ b/src/document.h @@ -4,6 +4,8 @@ #include "ipdf.h" #include "quadtree.h" +#include + #include "../contrib/pugixml-1.4/src/pugixml.hpp" #include "stb_truetype.h" @@ -50,17 +52,13 @@ namespace IPDF bool operator==(const Document & equ) const; bool operator!=(const Document & equ) const {return !(this->operator==(equ));} - unsigned AddGroup(unsigned start_index, unsigned end_index); + unsigned AddGroup(unsigned start_index, unsigned end_index, const Colour & shading=Colour(0.1,0.1,0.1,1)); unsigned AddBezier(const Bezier & bezier); unsigned Add(ObjectType type, const Rect & bounds, unsigned data_index = 0); unsigned AddBezierData(const Bezier & bezier); - + unsigned AddGroupData(const Group & group); - - - - /** SVG Related functions **/ /** Load an SVG text file and add to the document **/ @@ -70,10 +68,13 @@ namespace IPDF /** Parse an SVG node or SVG-group node, adding children to the document **/ void ParseSVGNode(pugi::xml_node & root, SVGMatrix & transform); /** Parse an SVG path with string **/ - std::pair ParseSVGPathData(const std::string & d, const SVGMatrix & transform); + std::pair ParseSVGPathData(const std::string & d, const SVGMatrix & transform, bool & closed); /** Modify an SVG transformation matrix **/ static void ParseSVGTransform(const std::string & s, SVGMatrix & transform); + + /** Extract CSS values (shudder) from style **/ + static void ParseSVGStyleData(const std::string & style, std::map & results); /** Font related functions **/ void SetFont(const std::string & font_filename); diff --git a/src/ipdf.h b/src/ipdf.h index ff31533..968a3ed 100644 --- a/src/ipdf.h +++ b/src/ipdf.h @@ -56,6 +56,8 @@ namespace IPDF struct Group { + unsigned start; + unsigned end; Colour shading; }; @@ -64,14 +66,12 @@ namespace IPDF /** Used by all objects **/ std::vector types; // types of objects std::vector bounds; // rectangle bounds of objects - - /** Used by BEZIER to identify data position in relevant vector **/ + /** Used by BEZIER and GROUP to identify data position in relevant vector **/ std::vector data_indices; - /** Used by BEZIER only **/ std::vector beziers; // bezier curves - look up by data_indices - - std::vector > groups; + /** Used by GROUP only **/ + std::vector groups; }; class View; diff --git a/src/objectrenderer.cpp b/src/objectrenderer.cpp index 8349bc1..967f7e1 100644 --- a/src/objectrenderer.cpp +++ b/src/objectrenderer.cpp @@ -227,12 +227,13 @@ void BezierRenderer::RenderUsingCPU(const Objects & objects, const View & view, Bezier control(objects.beziers[objects.data_indices[m_indexes[i]]].ToAbsolute(bounds),CPURenderBounds(Rect(0,0,1,1), view, target)); //Debug("%s -> %s via %s", objects.beziers[objects.data_indices[m_indexes[i]]].Str().c_str(), control.Str().c_str(), bounds.Str().c_str()); // Draw a rectangle around the bezier for debugging the bounds rectangle calculations - /* - ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y, pix_bounds.x+pix_bounds.w, pix_bounds.y, target, Colour(1,0,0,1)); - ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y+pix_bounds.h, pix_bounds.x+pix_bounds.w, pix_bounds.y+pix_bounds.h, target, Colour(0,1,0,1)); - ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y, pix_bounds.x, pix_bounds.y+pix_bounds.h, target, Colour(1,0,0,1)); - ObjectRenderer::RenderLineOnCPU(pix_bounds.x+pix_bounds.w, pix_bounds.y, pix_bounds.x+pix_bounds.w, pix_bounds.y+pix_bounds.h, target, Colour(0,1,0,1)); - */ + if (view.ShowingObjectBounds()) + { + ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y, pix_bounds.x+pix_bounds.w, pix_bounds.y, target, Colour(1,0,0,1)); + ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y+pix_bounds.h, pix_bounds.x+pix_bounds.w, pix_bounds.y+pix_bounds.h, target, Colour(0,1,0,1)); + ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y, pix_bounds.x, pix_bounds.y+pix_bounds.h, target, Colour(1,0,0,1)); + ObjectRenderer::RenderLineOnCPU(pix_bounds.x+pix_bounds.w, pix_bounds.y, pix_bounds.x+pix_bounds.w, pix_bounds.y+pix_bounds.h, target, Colour(0,1,0,1)); + } // Draw lines between the control points for debugging //ObjectRenderer::RenderLineOnCPU((int64_t)control.x0, (int64_t)control.y0, (int64_t)control.x1, (int64_t)control.y1,target); //ObjectRenderer::RenderLineOnCPU((int64_t)control.x1, (int64_t)control.y1, (int64_t)control.x2, (int64_t)control.y2,target); @@ -242,8 +243,7 @@ void BezierRenderer::RenderUsingCPU(const Objects & objects, const View & view, Real x[2]; Real y[2]; control.Evaluate(x[0], y[0], Real(0)); //Debug("target is (%lu, %lu)", target.w, target.h); - int64_t blen = 100; - //blen = min(max((int64_t)2, (int64_t)(target.w/view.GetBounds().w)), (int64_t)100); + int64_t blen = min(max((int64_t)2, (int64_t)(target.w/view.GetBounds().w)), (int64_t)100); Real invblen(1); invblen /= blen; //Debug("Using %li lines, inverse %f", blen, Double(invblen)); @@ -336,6 +336,9 @@ void BezierRenderer::RenderUsingGPU(unsigned first_obj_id, unsigned last_obj_id) */ void GroupRenderer::RenderUsingCPU(const Objects & objects, const View & view, const CPURenderTarget & target, unsigned first_obj_id, unsigned last_obj_id) { + if (!view.ShowingObjectBounds() && !view.PerformingShading()) + return; + for (unsigned i = 0; i < m_indexes.size(); ++i) { if (m_indexes[i] < first_obj_id) continue; @@ -344,22 +347,21 @@ void GroupRenderer::RenderUsingCPU(const Objects & objects, const View & view, c Rect bounds(CPURenderBounds(objects.bounds[m_indexes[i]], view, target)); PixelBounds pix_bounds(bounds); - + + const Group & group = objects.groups[objects.data_indices[m_indexes[i]]]; + const Colour & c = group.shading; + if (c.a == 0 || !view.PerformingShading()) + continue; - Colour c(0.5,0.5,1,1); // make the bounds just a little bit bigger pix_bounds.x--; pix_bounds.w++; pix_bounds.y--; pix_bounds.h++; - /* - ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y, pix_bounds.x+pix_bounds.w, pix_bounds.y, target, c); - ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y+pix_bounds.h, pix_bounds.x+pix_bounds.w, pix_bounds.y+pix_bounds.h, target, c); - ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y, pix_bounds.x, pix_bounds.y+pix_bounds.h, target, c); - ObjectRenderer::RenderLineOnCPU(pix_bounds.x+pix_bounds.w, pix_bounds.y, pix_bounds.x+pix_bounds.w, pix_bounds.y+pix_bounds.h, target, c); - */ + // Attempt to shade the region // Assumes the outline has been drawn first... + //#ifdef SHADING_DUMB for (int64_t y = max((int64_t)0, pix_bounds.y); y <= min(pix_bounds.y+pix_bounds.h, target.h-1); ++y) { bool inside = false; @@ -387,6 +389,15 @@ void GroupRenderer::RenderUsingCPU(const Objects & objects, const View & view, c } } } + //#endif //SHADING_DUMB + if (view.ShowingObjectBounds()) + { + + ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y, pix_bounds.x+pix_bounds.w, pix_bounds.y, target, c); + ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y+pix_bounds.h, pix_bounds.x+pix_bounds.w, pix_bounds.y+pix_bounds.h, target, c); + ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y, pix_bounds.x, pix_bounds.y+pix_bounds.h, target, c); + ObjectRenderer::RenderLineOnCPU(pix_bounds.x+pix_bounds.w, pix_bounds.y, pix_bounds.x+pix_bounds.w, pix_bounds.y+pix_bounds.h, target, c); + } } @@ -472,14 +483,13 @@ void ObjectRenderer::RenderLineOnCPU(int64_t x0, int64_t y0, int64_t x1, int64_t // TODO: Avoid extra inner conditionals do - { + { if (x >= 0 && x < width && y >= 0 && y < height) { int64_t index = (transpose ? (y + x*target.w)*4 : (x + y*target.w)*4); for (int i = 0; i < 4; ++i) target.pixels[index+i] = rgba[i]; } - if (p < 0) p += two_dy; else diff --git a/src/view.cpp b/src/view.cpp index d7d1e34..48e0a5a 100644 --- a/src/view.cpp +++ b/src/view.cpp @@ -20,7 +20,8 @@ using namespace std; View::View(Document & document, Screen & screen, const Rect & bounds, const Colour & colour) : m_use_gpu_transform(USE_GPU_TRANSFORM), m_use_gpu_rendering(USE_GPU_RENDERING), m_bounds_dirty(true), m_buffer_dirty(true), m_render_dirty(true), m_document(document), m_screen(screen), m_cached_display(), m_bounds(bounds), m_colour(colour), m_bounds_ubo(), - m_objbounds_vbo(), m_object_renderers(NUMBER_OF_OBJECT_TYPES), m_cpu_rendering_pixels(NULL) + m_objbounds_vbo(), m_object_renderers(NUMBER_OF_OBJECT_TYPES), m_cpu_rendering_pixels(NULL), + m_show_object_bounds(false), m_perform_shading(USE_SHADING) { Debug("View Created - Bounds => {%s}", m_bounds.Str().c_str()); @@ -71,7 +72,7 @@ void View::Translate(Real x, Real y) y *= m_bounds.h; m_bounds.x += x; m_bounds.y += y; - Debug("View Bounds => %s", m_bounds.Str().c_str()); + //Debug("View Bounds => %s", m_bounds.Str().c_str()); if (!m_use_gpu_transform) m_buffer_dirty = true; m_bounds_dirty = true; diff --git a/src/view.h b/src/view.h index fe93e6c..56e80a3 100644 --- a/src/view.h +++ b/src/view.h @@ -6,8 +6,9 @@ #include "framebuffer.h" #include "objectrenderer.h" -#define USE_GPU_TRANSFORM true -#define USE_GPU_RENDERING true +#define USE_GPU_TRANSFORM false +#define USE_GPU_RENDERING false +#define USE_SHADING !(USE_GPU_RENDERING) && true namespace IPDF { @@ -41,6 +42,11 @@ namespace IPDF void SetGPURendering(bool state) {m_use_gpu_rendering = state; m_bounds_dirty = true; m_buffer_dirty = true;} + bool ShowingObjectBounds() const {return m_show_object_bounds;} // render bounds rectangles + void ShowObjectBounds(bool state) {m_show_object_bounds = state; m_bounds_dirty = true; m_buffer_dirty = true;} + + bool PerformingShading() const {return m_perform_shading;} + void PerformShading(bool state) {m_perform_shading = state; m_bounds_dirty = true; m_buffer_dirty = true;} void ForceBoundsDirty() {m_bounds_dirty = true;} void ForceBufferDirty() {m_buffer_dirty = true;} @@ -78,6 +84,10 @@ namespace IPDF // Trust me it will be easier to generalise things this way. Even though there are pointers. std::vector m_object_renderers; uint8_t * m_cpu_rendering_pixels; // pixels to be used for CPU rendering + + // Debug rendering + bool m_show_object_bounds; + bool m_perform_shading; #ifndef QUADTREE_DISABLED QuadTreeIndex m_current_quadtree_node; // The highest node we will traverse. -- 2.20.1