├── .gitignore ├── README.md ├── images └── birds.png └── source └── svgPeckPDE ├── Cursor.pde ├── LazyBrush.pde ├── LazyPoint.pde ├── Stroke.pde ├── StrokeManager.pde ├── TimestampFactory.pde └── svgPeckPDE.pde /.gitignore: -------------------------------------------------------------------------------- 1 | _SVG/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SVGpeck 2 | 3 | Sketch directly with vectors using a mouse or pen tablet. Save your drawings as SVG. 4 | 5 | If you own a pen plotter and you like to draw, this might be for you. 6 | 7 | Note: SVGpeck is a minimalist piece of software with a very narrow use case. I couldn't find any tool that did precisely what I needed so I built it for my own usage (sending my birdie doodles to an AxiDraw). I hope some of you find it helpful too. 8 | 9 | 🦜 10 | R. 11 | 12 | PS: If you enjoy SVGpeck, give it a star. And if your gratefulness needs another outlet, you can always buy me a coffee buy clicking the button below 😉 13 | 14 | [![ko-fi](https://www.ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/M4M32WNJ5) 15 | 16 | 17 | ## Setup 18 | - Install [Processing](http://processing.org/). 19 | - Clone this repository 20 | - Open svgPeckPDE.pde 21 | - Adjust the parameters in the code 22 | - Run the sketch 23 | 24 | ## Shortcuts 25 | - "Z" : undo 26 | - "Y" : redo 27 | - "D" : show/hide debug information 28 | - "S" : save the current drawing as an SVG 29 | - "M" : show/hide the magnifying glass 30 | 31 | ## Notes 32 | SVG files are automatically saved in an _SVG folder inside the Processing sketch folder. 33 | 34 | 35 | ![Line sketch of a bird drawing a bird on the floor with a quill while another bird is watching](https://github.com/SableRaf/svgpeck/blob/main/images/birds.png "Trace") 36 | -------------------------------------------------------------------------------- /images/birds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SableRaf/svgpeck/b4024f8f83c4721a5a6daca5beddc97d212aa077/images/birds.png -------------------------------------------------------------------------------- /source/svgPeckPDE/Cursor.pde: -------------------------------------------------------------------------------- 1 | class Cursor { 2 | 3 | float x, y, s, px, py; 4 | boolean isActive = false; 5 | float v = 0.0; // velocity 6 | color c, activeColor, idleColor; 7 | ArrayList mag; 8 | final int magMaxSize = 10; 9 | 10 | Cursor(){ 11 | this.x = 0; 12 | this.y = 0; 13 | this.s = 4; 14 | activeColor = color(#4898F7); 15 | idleColor = color(0); 16 | mag = new ArrayList(); 17 | } 18 | 19 | void setPos(float _x, float _y) 20 | { 21 | this.px = this.x; 22 | this.py = this.y; 23 | this.x = _x; 24 | this.y = _y; 25 | } 26 | 27 | PVector getPos() 28 | { 29 | return new PVector(this.x,this.y); 30 | } 31 | 32 | PVector getPreviousPos() { 33 | return new PVector(this.px, this.py); 34 | } 35 | 36 | void setActive(boolean _a) 37 | { 38 | this.isActive = _a; 39 | this.update(); 40 | } 41 | 42 | void setSize(float _s) 43 | { 44 | this.s = _s; 45 | } 46 | 47 | float getSize() 48 | { 49 | return this.s; 50 | } 51 | 52 | void update() 53 | { 54 | if(isActive) 55 | { 56 | c = activeColor; 57 | } else { 58 | c = idleColor; 59 | } 60 | calculateAverageVelocity(); 61 | } 62 | 63 | float getVelocity(){ 64 | return this.v; 65 | } 66 | 67 | void calculateAverageVelocity() 68 | { 69 | 70 | PVector direction = PVector.sub(getPos(),getPreviousPos()); 71 | float magnitude = direction.mag(); 72 | 73 | mag.add(magnitude); 74 | 75 | if( mag.size() > magMaxSize) { mag.remove(0); } 76 | 77 | float acc = 0.0; 78 | for(int i=0; i this.radius) { 194 | this.brush.moveByAngle(this.angle, this.distance - this.radius); 195 | this.hasMoved = true; 196 | 197 | //println("this.distance > this.radius == true" + " | [" + millis() + "]"); 198 | } 199 | } else { 200 | this.distance = 0; 201 | this.angle = 0; 202 | this.brush.update(newX,newY); 203 | this.hasMoved = true; 204 | //println("this.isEnabled == false" + " | [" + millis() + "]"); 205 | } 206 | // println(""); 207 | 208 | return true; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /source/svgPeckPDE/LazyPoint.pde: -------------------------------------------------------------------------------- 1 | // Ported from: https://github.com/dulnan/lazy-brush 2 | // https://www.reddit.com/r/javascript/comments/9paoyp/lazybrush_smooth_canvas_drawing_with_a_mouse_or/ 3 | 4 | /* 5 | "Define a "lazy radius" around the brush, 6 | let's say 100px. Now every time the mouse moves, 7 | check the distance between mouse and brush. 8 | If this distance is 105px, move the brush by 5px 9 | in the direction of the mouse. To achieve this, 10 | you have to calculate the angle between mouse 11 | and brush. With the angle and the difference, using 12 | a bit of sine and cosine, you can determine 13 | the new coordinates for the brush." 14 | */ 15 | 16 | class LazyPoint extends PVector 17 | { 18 | 19 | //float x, y; 20 | 21 | LazyPoint(float x, float y) 22 | { 23 | this.update(x,y); 24 | } 25 | 26 | /** 27 | * Update the x and y values 28 | * 29 | * @param {PVector} point 30 | */ 31 | void update (float x, float y) { 32 | this.x = x; 33 | this.y = y; 34 | } 35 | 36 | /** 37 | * Move the point to another position using an angle and distance 38 | * 39 | * @param {float} angle The angle in radians 40 | * @param {float} distance How much the point should be moved 41 | */ 42 | void moveByAngle (float angle, float distance) { 43 | // Rotate the angle based on the coordinate system ([0,0] in the top left) 44 | final float angleRotated = angle + (PI / 2); 45 | 46 | this.x = this.x + (sin(angleRotated) * distance); 47 | this.y = this.y - (cos(angleRotated) * distance); 48 | } 49 | 50 | /** 51 | * Check if this point is the same as another point 52 | * 53 | * @param {PVector} point 54 | * @returns {boolean} 55 | */ 56 | boolean equalsTo (PVector point) { 57 | return (this.x == point.x && this.y == point.y); 58 | } 59 | 60 | /** 61 | * Get the difference for x and y axis to another point 62 | * 63 | * @param {PVector} point 64 | * @returns {PVector} 65 | */ 66 | PVector getDifferenceTo (PVector point) { 67 | return new PVector(this.x - point.x, this.y - point.y); 68 | } 69 | 70 | /** 71 | * Calculate distance to another point 72 | * 73 | * @param {PVector} point 74 | * @returns {PVector} 75 | */ 76 | float getDistanceTo (PVector point) { 77 | final PVector diff = this.getDifferenceTo(point); 78 | 79 | return sqrt(pow(diff.x, 2) + pow(diff.y, 2)); 80 | } 81 | 82 | /** 83 | * Calculate the angle to another point 84 | * 85 | * @param {PVector} point 86 | * @returns {float} 87 | */ 88 | float getAngleTo (PVector point) { 89 | final PVector diff = this.getDifferenceTo(point); 90 | 91 | return atan2(diff.y, diff.x); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /source/svgPeckPDE/Stroke.pde: -------------------------------------------------------------------------------- 1 | class Stroke 2 | { 3 | int strokeAmount; 4 | int pointAmount; // length of the Point Array (max number of points) 5 | int pointsAssigned = 0; // amount of points currently in the Point array (acts as a cursor) 6 | float radiusDivide = 10; // distance between current and next point / this = radius for first half of the stroke 7 | ArrayList points; 8 | float stepDistance = 0.0; // higher value means smoother curve (TO DO correct edge effects for higher values) 9 | 10 | 11 | Stroke() 12 | { 13 | init(); 14 | } 15 | 16 | void init() 17 | { 18 | points = new ArrayList(); // Create the array that will hold the points composing the stroke 19 | } 20 | 21 | void addPoint(PVector _p) 22 | { 23 | points.add(_p); 24 | //points.get(points.size() - 1).px = currentX; 25 | //points.get(points.size() - 1).py = currentY; 26 | } 27 | 28 | void setStepDistance(float _s) 29 | { 30 | this.stepDistance = _s; 31 | } 32 | 33 | PVector getPoint(int i){ 34 | PVector p = new PVector(0.0,0.0); 35 | int size = points.size(); 36 | if(size < 1) 37 | { 38 | println("Error: Trying to getPoint() while the points ArrayList is empty. [millis:" + millis() +"]" ); 39 | //exit(); 40 | } 41 | else if (i < size) 42 | { 43 | p = points.get(i); 44 | } 45 | else 46 | { 47 | println("Index out of bounds at getPoint(). points.size() : " + size + " index : " + i); 48 | exit(); 49 | } 50 | return p; 51 | } 52 | 53 | int getSize() 54 | { 55 | if(points != null) { return points.size(); } 56 | else 57 | { 58 | println("Trying to getSize() of points array while points array has not been instanciated"); 59 | return 0; 60 | } 61 | } 62 | 63 | //void erase() { 64 | // points.clear(); 65 | //} 66 | 67 | void display(PGraphics _pg, float _s) 68 | { 69 | PGraphics targetBuffer = _pg; 70 | float scale = _s; 71 | 72 | targetBuffer.beginShape(); 73 | PVector p = this.getPoint(0); 74 | PVector lastPointDrawn = p; 75 | targetBuffer.curveVertex(p.x,p.y); 76 | targetBuffer.curveVertex(p.x,p.y); 77 | for (int i = 0; i < this.getSize()-1; i++) 78 | { 79 | p = this.getPoint(i); 80 | float dist = dist(p.x, p.y, lastPointDrawn.x, lastPointDrawn.y); 81 | if(dist > stepDistance) 82 | { 83 | targetBuffer.curveVertex(p.x, p.y); 84 | //println("Drawing curveVertex at x:"+p.x+" y:"+p.y); 85 | lastPointDrawn = p; 86 | } 87 | } 88 | p = this.getPoint(this.getSize()-1); 89 | targetBuffer.curveVertex(p.x,p.y); 90 | targetBuffer.curveVertex(p.x,p.y); 91 | targetBuffer.endShape(); 92 | 93 | if(isDebug) 94 | { 95 | for (int i = 0; i < this.getSize() - 1; i++) 96 | { 97 | p = this.getPoint(i); 98 | float dist = dist(p.x, p.y, lastPointDrawn.x, lastPointDrawn.y); 99 | if(dist > stepDistance || i == 0) 100 | { 101 | targetBuffer.pushStyle(); 102 | targetBuffer.noStroke(); 103 | targetBuffer.fill(#FCF10F); 104 | targetBuffer.circle(p.x,p.y, 3 * scale); 105 | targetBuffer.popStyle(); 106 | lastPointDrawn = p; 107 | } 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /source/svgPeckPDE/StrokeManager.pde: -------------------------------------------------------------------------------- 1 | class StrokeManager 2 | { 3 | int cursor = 0; // strokes should be drawn until this index (used for undo and redo) 4 | Stroke activeStroke; 5 | ArrayList strokeList; 6 | 7 | final float DIST_THRESHOLD = 0.5; // defines how much the pointer need to move before we add a new point 8 | 9 | float smoothFactor = 0.0; 10 | 11 | StrokeManager() 12 | { 13 | init(); 14 | } 15 | 16 | void init() 17 | { 18 | this.strokeList = new ArrayList(); 19 | } 20 | 21 | void addStroke() 22 | { 23 | clearRedoStack(); 24 | this.activeStroke = new Stroke(); 25 | this.activeStroke.setStepDistance(smoothFactor); 26 | strokeList.add(this.activeStroke); 27 | cursor++; 28 | } 29 | 30 | void display() 31 | { 32 | PGraphics defaultPGraphics = g; 33 | this.display(defaultPGraphics); 34 | } 35 | 36 | void display(PGraphics _pg) 37 | { 38 | float defaultScale = 1.0; 39 | this.display(_pg, defaultScale); 40 | } 41 | 42 | void display(PGraphics _pg, float _s) 43 | { 44 | PGraphics targetBuffer = _pg; 45 | float scale = _s; 46 | //stroke.display(); 47 | for(int i=0; i0) cursor--; 69 | else println("Nothing left to UNdo"); 70 | } 71 | 72 | void redo() 73 | { 74 | if(cursor cursor) 83 | { 84 | int i = strokeList.size()-1; 85 | strokeList.remove(i); 86 | } 87 | } 88 | else 89 | { 90 | if(isDebug) println("Nothing to clear from the redo stack"); 91 | } 92 | } 93 | 94 | void addPoint(PVector _p) { // Pass the current coordinates to the strokes 95 | 96 | PVector p = _p; 97 | 98 | //int i = strokeList.size()-1; 99 | //Stroke lastStroke = strokeList.get(i); 100 | 101 | int slidingAverageSize = 6; 102 | 103 | Boolean isBegin = this.activeStroke.points.size() < slidingAverageSize; // exclude the first few points 104 | 105 | PVector acc = new PVector(0.0,0.0); // accumulator 106 | 107 | if(isBegin) 108 | { 109 | this.activeStroke.addPoint(p); // For the first few points, we just add one 110 | } 111 | else 112 | { 113 | int minIndex = this.activeStroke.points.size() - slidingAverageSize; 114 | for(int index = this.activeStroke.points.size()-1; index > minIndex; index--) 115 | { 116 | PVector pt = this.activeStroke.getPoint(index); 117 | acc = acc.add(pt); 118 | } 119 | PVector ap = acc.div(slidingAverageSize-1); // average position of the previous points 120 | 121 | // Check if the new coordinates are sufficiently different from the past average 122 | Boolean isMouseMoved = dist(p.x, p.y, ap.x, ap.y) > DIST_THRESHOLD; 123 | 124 | if(isMouseMoved) 125 | { 126 | this.activeStroke.addPoint(p); 127 | if(isDebug) println("adding a point at x:" + p.x + " y:" + p.y); 128 | } 129 | else println("mouse stopped moving"); 130 | 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /source/svgPeckPDE/TimestampFactory.pde: -------------------------------------------------------------------------------- 1 | class TimestampFactory 2 | { 3 | 4 | TimestampFactory() { 5 | 6 | } 7 | 8 | String getString() { 9 | 10 | int days = day(); 11 | int months = month(); 12 | int years = year(); 13 | int hours = hour(); 14 | int minutes = minute(); 15 | int seconds = second(); 16 | int millis = millis(); 17 | 18 | String dayFormat = nf(days, 2); 19 | String monthFormat = nf(months, 2); 20 | String yearFormat = nf(years, 4); 21 | String hoursFormat = nf(hours, 2); 22 | String minutesFormat = nf(minutes, 2); 23 | String secondsFormat = nf(seconds, 2); 24 | String millisFormat = nf(millis, 2); 25 | 26 | String stamp = yearFormat + monthFormat + dayFormat 27 | + "_" 28 | + hoursFormat + minutesFormat + secondsFormat 29 | + "_" 30 | + millisFormat 31 | ; 32 | 33 | return stamp; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /source/svgPeckPDE/svgPeckPDE.pde: -------------------------------------------------------------------------------- 1 | // Troubleshooting: //<>// 2 | // If the stroke does not start immediately when the Wacom pen is pressed but the mouse works fine. 3 | // This is a Windows issue, uncheck "Use Windows Ink" in the Wacom Tablet Properties. 4 | // The checkbox can be found at the bottom left of the "Mapping" tab. 5 | 6 | // TO DO 7 | // - clear (undoable how?) 8 | // - optimize drawing to only render new part of the stroke 9 | // - get rid of the draw loop (?) 10 | // - hatch fill 11 | // - variable strokeWeight (with hatch fill) 12 | // - brush styles (e.g.dotted lines, fur, grass) 13 | // - drop svg to use as brush style? 14 | // - undo part of a stroke (press undo and use the pen to define how much to remove, scrubbing up and down) 15 | // - double strokes and reverse strokes as options 16 | // - hold for straight line 17 | 18 | // CHANGELOG: 19 | // 07.12.2020: 20 | // - Implemented lazy brush https://lazybrush.dulnan.net 21 | // - Made the lazy radius dynamic 22 | // - Use an off screen buffer for sub-pixel precision 23 | // 08.12.2020 24 | // - Added magnifier 25 | // - adjustable smoothness and lazyradius based on pointer velocity (improves accuracy) 26 | // 09.12.2020 27 | // - Switched to P2D (Duh) 28 | // 11.12.20 29 | // - Added save dialog on Ctrl+S (Typing 's' by itself quick saves into the _SVG folder) 30 | 31 | import processing.svg.*; 32 | 33 | final float LAZY_RADIUS_MIN = 0.0; 34 | final float LAZY_RADIUS_MAX = 20.0; 35 | final float LAZY_RAMP = 50.0; // at which cursor velocity do we reach LAZY_RADIUS_MAX 36 | final float SMOOTHING_MIN = 0.0; 37 | final float SMOOTHING_MAX = 0.0; 38 | final float SMOOTHING_RAMP = 1.0; // at which cursor velocity do we reach SMOOTHING_MAX 39 | final float STROKE_WEIGHT = 2; 40 | final float SCALE_MULTIPLIER = 3.0; 41 | final boolean LAZY_BRUSH = true; 42 | 43 | PGraphics pgr; // raster image 44 | float magnifierScale = SCALE_MULTIPLIER*0.5; // super sampling multiplier 45 | int superWidth, superHeight; 46 | 47 | float startX = 0.0; 48 | float startY = 0.0; 49 | 50 | PGraphics magnifier; 51 | float magnifierSize = 0.2; 52 | boolean showMagnifier = false; 53 | 54 | Boolean isDebug = false; 55 | Boolean isDrawing = false; 56 | Boolean isSaveSVG = false; 57 | Boolean isRender = true; 58 | Boolean isSelectFile = false; 59 | Boolean isCtrlPressed = false; 60 | 61 | String fileSavePath; 62 | 63 | StrokeManager strokes; 64 | 65 | TimestampFactory timestamp; 66 | 67 | Cursor cursor; 68 | 69 | LazyBrush lazy; 70 | 71 | 72 | void setup() { 73 | size(1200, 1200, P2D); 74 | frameRate(60); 75 | noCursor(); //<>// 76 | 77 | this.strokes = new StrokeManager(); 78 | this.timestamp = new TimestampFactory(); 79 | this.cursor = new Cursor(); 80 | this.lazy = new LazyBrush(); 81 | this.lazy.enable(); 82 | 83 | this.fileSavePath = generateFilePath(); 84 | 85 | this.superWidth = floor(width*SCALE_MULTIPLIER); 86 | this.superHeight = floor(height*SCALE_MULTIPLIER); 87 | this.pgr = createGraphics( superWidth, superHeight ); 88 | this.magnifier = createGraphics(floor(width*magnifierSize), floor(height*magnifierSize)); 89 | 90 | this.lazy.setRadius(LAZY_RADIUS_MIN * this.SCALE_MULTIPLIER); 91 | 92 | this.strokes.setSmooth(SMOOTHING_MIN * this.SCALE_MULTIPLIER); 93 | } 94 | 95 | void draw() { 96 | 97 | background(255); 98 | 99 | float superX = mouseX * this.SCALE_MULTIPLIER; 100 | float superY = mouseY * this.SCALE_MULTIPLIER; 101 | 102 | this.lazy.update(superX, superY); 103 | 104 | LazyPoint brush = this.lazy.getBrush(); // current coordinates 105 | LazyPoint pointer = lazy.getPointer(); // should hold the same values as mouseX, mouseY 106 | float lazyRadius = lazy.getRadius(); 107 | 108 | float cursorVelocity = this.cursor.getVelocity(); 109 | 110 | float scaledRadiusMin = LAZY_RADIUS_MIN * SCALE_MULTIPLIER; 111 | float scaledRadiusMax = LAZY_RADIUS_MAX * SCALE_MULTIPLIER; 112 | lazyRadius = constrain(map(cursorVelocity, 0, LAZY_RAMP, scaledRadiusMin, scaledRadiusMax), 0.0, scaledRadiusMax); 113 | this.lazy.setRadius(lazyRadius); 114 | 115 | this.strokes.setSmooth(map(cursorVelocity, 0.0, SMOOTHING_RAMP, SMOOTHING_MIN*SCALE_MULTIPLIER, SMOOTHING_MAX*SCALE_MULTIPLIER)); 116 | 117 | if (isDrawing) 118 | { 119 | 120 | if (LAZY_BRUSH) { 121 | this.strokes.addPoint(new PVector(brush.x, brush.y)); 122 | } else { 123 | this.strokes.addPoint(new PVector(mouseX, mouseY)); 124 | } 125 | 126 | this.cursor.setActive(true); 127 | } else 128 | { 129 | this.cursor.setActive(false); 130 | } 131 | 132 | if (isRender) 133 | { 134 | drawScreen(pgr, SCALE_MULTIPLIER); 135 | this.isRender = false; 136 | } 137 | 138 | image(this.pgr, 0, 0, width, height); 139 | 140 | if (this.isSelectFile) { 141 | selectOutput("Select a file to write to:", "fileSelected"); 142 | this.isSelectFile = false; 143 | } 144 | 145 | if (this.isSaveSVG) { 146 | drawSVG(floor(this.superWidth), floor(this.superHeight), this.fileSavePath); 147 | println("File was saved to " + this.fileSavePath); 148 | this.isSaveSVG = false; 149 | } 150 | 151 | if (showMagnifier) 152 | { 153 | // Show magnified view at the pointer position 154 | int mw = magnifier.width; 155 | int mh = magnifier.height; 156 | int focusX = isDrawing ? floor(startX) : floor(lazy.getBrush().x); 157 | int focusY = isDrawing ? floor(startY) : floor(lazy.getBrush().y); 158 | int mx = constrain(focusX, 0, this.superWidth); 159 | int my = constrain(focusY, 0, this.superHeight); 160 | this.magnifier.beginDraw(); 161 | this.magnifier.image(this.pgr.get(floor(mx-mw/2), floor(my-mh/2), floor(mx+mw/2), floor(my+mh/2)), 0, 0); 162 | this.magnifier.stroke(0); 163 | this.magnifier.noFill(); 164 | this.magnifier.rect(0, 0, mw-1, mh-1); 165 | this.magnifier.circle(mw/2, mh/2, cursor.getSize()/2*SCALE_MULTIPLIER); 166 | this.magnifier.endDraw(); 167 | image(this.magnifier, 10, 10); 168 | } 169 | 170 | //LazyPoint brush = lazy.getBrush(); // current coordinates 171 | 172 | 173 | float screenRadius = lazyRadius / SCALE_MULTIPLIER; 174 | float screenBrushX = brush.x / SCALE_MULTIPLIER; 175 | float screenBrushY = brush.y / SCALE_MULTIPLIER; 176 | float screenPointerX = pointer.x / SCALE_MULTIPLIER; 177 | float screenPointerY = pointer.y / SCALE_MULTIPLIER; 178 | 179 | if (isDebug) 180 | { 181 | // Show the position of the brush 182 | pushStyle(); 183 | noStroke(); 184 | fill(#28CAF5); 185 | circle( screenBrushX, screenBrushY, 6 ); 186 | popStyle(); 187 | 188 | // Show the radius 189 | pushStyle(); 190 | noFill(); 191 | strokeWeight(4); 192 | stroke(150); 193 | circle( screenBrushX, screenBrushY, screenRadius*2 ); 194 | popStyle(); 195 | 196 | // Show the position of the pointer 197 | pushStyle(); 198 | noStroke(); 199 | fill(#CE6BEA); 200 | circle( screenPointerX, screenPointerY, 6); 201 | popStyle(); 202 | } 203 | 204 | pushStyle(); 205 | strokeWeight(1); 206 | stroke(0); 207 | //this.cursor.setPos(screenBrushX,screenBrushY); 208 | this.cursor.setPos(screenPointerX, screenPointerY); 209 | this.cursor.display(); 210 | popStyle(); 211 | 212 | lazy.reset(); 213 | 214 | noLoop(); 215 | } 216 | 217 | void startDrawing() { 218 | startX = this.lazy.getBrush().x; 219 | startY = this.lazy.getBrush().y; 220 | strokes.addStroke(); 221 | isDrawing = true; 222 | lazy.enable(); 223 | } 224 | 225 | void finishDrawing() { 226 | isDrawing = false; 227 | lazy.disable(); 228 | } 229 | 230 | void renderFrame() 231 | { 232 | loop(); 233 | isRender = true; 234 | } 235 | 236 | void drawScreen(PGraphics _pg, float _scale) 237 | { 238 | _pg.beginDraw(); 239 | _pg.background(255); 240 | _pg.noFill(); 241 | _pg.strokeWeight(STROKE_WEIGHT * _scale); 242 | _pg.stroke(0); 243 | strokes.display(_pg, _scale); 244 | _pg.endDraw(); 245 | } 246 | 247 | void drawSVG(int _width, int _height, String _filename) 248 | { 249 | PGraphics svg = createGraphics( _width, _height, SVG, _filename); 250 | println("save svg: BEGIN"); 251 | boolean d = isDebug; // save the debug state to restore it after rendering the vectors 252 | if (d) { 253 | isDebug = false; 254 | } 255 | svg.beginDraw(); 256 | svg.noFill(); 257 | svg.stroke(0); 258 | strokes.display(svg, 1.0); 259 | svg.endDraw(); 260 | if (d) { 261 | isDebug = true; 262 | } 263 | println("save svg: END"); 264 | } 265 | 266 | String generateFilePath() 267 | { 268 | return this.fileSavePath = "_SVG/" + timestamp.getString() + ".svg"; 269 | } 270 | 271 | void fileSelected(File selection) 272 | { 273 | if (selection == null) { 274 | println("Save window was closed or the user hit cancel."); 275 | } else { 276 | this.fileSavePath = selection.getAbsolutePath(); 277 | this.isSaveSVG = true; 278 | } 279 | } 280 | 281 | void drawCursor(float _x, float _y, float _diameter) 282 | { 283 | pushStyle(); 284 | blendMode(DIFFERENCE); 285 | strokeWeight(1); 286 | stroke(0); 287 | circle(_x, _y, _diameter); 288 | popStyle(); 289 | } 290 | 291 | void keyPressed() { 292 | //renderFrame(); 293 | if (key == CODED) { 294 | if (keyCode == CONTROL) { 295 | this.isCtrlPressed = true; 296 | } 297 | } else { 298 | if (this.isCtrlPressed) { 299 | if (keyCode == 83) // 's' 300 | { 301 | this.isSelectFile = true; 302 | } 303 | } 304 | } 305 | } 306 | 307 | void keyReleased() 308 | { 309 | if (key == CODED) { 310 | if (keyCode == CONTROL) { 311 | this.isCtrlPressed = false; 312 | } 313 | } else { 314 | if (key == 's') // Quick save (no user dialog) 315 | { 316 | this.fileSavePath = generateFilePath(); 317 | this.isSaveSVG = true; 318 | } else if (key == 'z') { 319 | undo(); 320 | } else if (key == 'y') { 321 | redo(); 322 | } else if (key == 's') { 323 | this.isSaveSVG = true; 324 | } else if (key == 'd') { 325 | this.isDebug = !this.isDebug; 326 | } else if (key == 'm') { 327 | this.showMagnifier = !this.showMagnifier; 328 | } 329 | } 330 | renderFrame(); 331 | this.isDrawing=false; 332 | } 333 | 334 | void saveSVG() 335 | { 336 | this.isSaveSVG = true; 337 | } 338 | 339 | void undo() 340 | { 341 | strokes.undo(); 342 | } 343 | 344 | void redo() 345 | { 346 | strokes.redo(); 347 | } 348 | 349 | void mousePressed() { 350 | renderFrame(); 351 | startDrawing(); 352 | } 353 | 354 | void mouseDragged() { 355 | renderFrame(); 356 | } 357 | 358 | void mouseReleased() { 359 | finishDrawing(); 360 | } 361 | 362 | void mouseMoved() { 363 | //renderFrame(); 364 | loop(); 365 | } 366 | --------------------------------------------------------------------------------