Skip to content

Commit

Permalink
Merge pull request #21 from pastthepixels/optimisations
Browse files Browse the repository at this point in the history
Code cleanup and optimizations
  • Loading branch information
pastthepixels authored May 8, 2024
2 parents f2f5fc4 + bbe2a18 commit d7668a4
Show file tree
Hide file tree
Showing 19 changed files with 349 additions and 143 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ dependencies {
implementation 'com.github.martin-stone:hsv-alpha-color-picker-android:3.0.1'
implementation 'androidx.preference:preference:1.2.1'
implementation 'com.takisoft.preferencex:preferencex:1.1.0'
implementation 'dev.romainguy:pathway:0.10.0'
implementation 'dev.romainguy:pathway:0.18.0'
/* End custom modules */
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
Expand Down
158 changes: 98 additions & 60 deletions app/src/main/java/io/github/pastthepixels/freepaint/File/SVG.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import android.annotation.SuppressLint;
import android.graphics.Color;
import android.util.Log;

import androidx.annotation.NonNull;

Expand All @@ -18,17 +17,16 @@
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.util.LinkedList;
import java.util.Objects;
import java.util.ArrayList;
import java.util.stream.Collectors;

import javax.xml.parsers.DocumentBuilderFactory;

import io.github.pastthepixels.freepaint.DrawAppearance;
import io.github.pastthepixels.freepaint.DrawCanvas;
import io.github.pastthepixels.freepaint.DrawPath;
import io.github.pastthepixels.freepaint.Point;
import dev.romainguy.graphics.path.Svg;
import io.github.pastthepixels.freepaint.Graphics.DrawAppearance;
import io.github.pastthepixels.freepaint.Graphics.DrawCanvas;
import io.github.pastthepixels.freepaint.Graphics.DrawPath;
import io.github.pastthepixels.freepaint.Graphics.Point;

public class SVG {
private final DrawCanvas canvas;
Expand Down Expand Up @@ -105,14 +103,7 @@ public void loadFile(InputStream stream) {
public void addPath(DrawPath path) {
StringBuilder data = new StringBuilder("\n<path d=\"");
// Step 1. Add points.
for (Point point : path.points) {
String command = point.command == Point.COMMANDS.move ? "M" : "L";
if (point == path.points.get(0)) command = "M";
data.append(String.format("%s%.2f %.2f ", command, point.x, point.y));
}
if (path.isClosed) {
data.append("Z");
}
data.append(Svg.toSvg(path.generatePath(), false));
data.append("\" ");
// Step 2. Set the appearance of the path.
// Inkscape only accepts hex colors, I don't know why
Expand Down Expand Up @@ -148,8 +139,8 @@ public void parseFile(String data) {
Document document = factory.newDocumentBuilder().parse(new ByteArrayInputStream(data.getBytes()));
// Set size/color
canvas.documentSize.set(
parseFloat(document.getDocumentElement().getAttribute("width")),
parseFloat(document.getDocumentElement().getAttribute("height"))
Float.parseFloat(document.getDocumentElement().getAttribute("width").replace("px", "")),
Float.parseFloat(document.getDocumentElement().getAttribute("height").replace("px", ""))
);
if (document.getDocumentElement().hasAttribute("viewport-fill")) {
canvas.documentColor = Color.parseColor(document.getDocumentElement().getAttribute("viewport-fill"));
Expand Down Expand Up @@ -182,7 +173,7 @@ public void parseFile(String data) {
path.appearance.strokeSize = Integer.parseInt(element.getAttribute("stroke-width"));
}
// Done!!
path.finalise();
path.cachePath();
canvas.paths.add(path);
}
}
Expand All @@ -207,13 +198,13 @@ public void parseFile(String data) {
* @param d The "d" attribute of an SVG path element.
* @return A linked list of points, with proper commands, that you can use in a DrawPath.
*/
public LinkedList<Point> parsePath(@NonNull String d) {
public ArrayList<Point> parsePath(@NonNull String d) {
// We start with a "pen" that we set the position of (capital letters) or move around by an amount (lowercase letters)
Point penCoords = new Point(0, 0);
LinkedList<Point> points = new LinkedList<>();
ArrayList<Point> points = new ArrayList<>();

// 1. Separate the string into 1 string per command (ex. "M 12 240 10 4" (multiple points with same command), "L 4")
LinkedList<String> commands = new LinkedList<>();
ArrayList<String> commands = new ArrayList<>();
for (int i = 0; i < d.length(); i++) {
if (Character.isLetter(d.charAt(i))) {
commands.add(String.valueOf(d.charAt(i)));
Expand All @@ -232,43 +223,114 @@ public LinkedList<Point> parsePath(@NonNull String d) {
Point.COMMANDS command = svgToPointCommand(commands.get(i).charAt(0));
boolean isCommandRelative = Character.isLowerCase(commands.get(i).charAt(0));
// Note: commas are ignored (as per W3 spec) and replaced with spaces
String[] numbers = commands.get(i).replace(",", " ").substring(1).strip().split(" ");
String[] numbers = commands.get(i).replace(",", " ").replace("-", " -").replace(" ", " ").substring(1).strip().split(" ");
//TODO remove
for (String num : numbers) {
System.out.print(num + " ");
}
System.out.println();

Point originalPenCoords; // Used for bezier curves

switch (command) {
// Horizontal lines ("H" command)
case horizontal:
penCoords.x = isCommandRelative ? penCoords.x + parseFloat(numbers[0]) : parseFloat(numbers[0]);
penCoords.x = Float.parseFloat(numbers[0]) + (isCommandRelative ? penCoords.x : 0);
points.add(new Point(penCoords.x, penCoords.y, Point.COMMANDS.line));
break;

// Vertical lines ("V" command)
// Vertical lines ("V" command)
case vertical:
penCoords.y = isCommandRelative ? penCoords.y + parseFloat(numbers[0]) : parseFloat(numbers[0]);
penCoords.y = Float.parseFloat(numbers[0]) + (isCommandRelative ? penCoords.y : 0);
points.add(new Point(penCoords.x, penCoords.y, Point.COMMANDS.line));
break;

// Moveto command ("M")
// Moveto command ("M")
case move:
for (int j = 0; j < numbers.length; j++) {
// Even index: likely x coordinate
if (j % 2 == 0) {
penCoords.x = isCommandRelative ? penCoords.x + parseFloat(numbers[j]) : parseFloat(numbers[j]);
penCoords.x = Float.parseFloat(numbers[j]) + (isCommandRelative ? penCoords.x : 0);
} else {
penCoords.y = isCommandRelative ? penCoords.y + parseFloat(numbers[j]) : parseFloat(numbers[j]);
penCoords.y = Float.parseFloat(numbers[j]) + (isCommandRelative ? penCoords.y : 0);
}
}
points.add(new Point(penCoords.x, penCoords.y, Point.COMMANDS.move));
break;

// Lineto command ("L")
// Lineto command ("L")
case line:
for (int j = 0; j < numbers.length; j++) {
// Even index: likely x coordinate
if (j % 2 == 0) {
penCoords.x = isCommandRelative ? penCoords.x + parseFloat(numbers[j]) : parseFloat(numbers[j]);
penCoords.x = Float.parseFloat(numbers[j]) + (isCommandRelative ? penCoords.x : 0);
} else {
// Odd index: y component of a coordinate, completes
// a coordinate which we add as a Point.
penCoords.y = isCommandRelative ? penCoords.y + parseFloat(numbers[j]) : parseFloat(numbers[j]);
penCoords.y = Float.parseFloat(numbers[j]) + (isCommandRelative ? penCoords.y : 0);
points.add(new Point(penCoords.x, penCoords.y, Point.COMMANDS.line));
}
}
break;

// Cubic bezier curve command ("C")
case cubicBezier:
originalPenCoords = points.get(points.size() - 1).clone();
for (int j = 0; j < numbers.length - 5; j += 6) {
// Sets the right handle of the last point.
penCoords.x = Float.parseFloat(numbers[j]) + (isCommandRelative ? originalPenCoords.x : 0);
penCoords.y = Float.parseFloat(numbers[j + 1]) + (isCommandRelative ? originalPenCoords.y : 0);
points.get(points.size() - 1).setRightHandle(new Point(
penCoords.x - points.get(points.size() - 1).x,
penCoords.y - points.get(points.size() - 1).y
));
// Defines the handle for a new point.
penCoords.x = Float.parseFloat(numbers[j + 2]) + (isCommandRelative ? originalPenCoords.x : 0);
penCoords.y = Float.parseFloat(numbers[j + 3]) + (isCommandRelative ? originalPenCoords.y : 0);
Point leftHandle = new Point(
penCoords.x,
penCoords.y
);
// Makes a new point.
penCoords.x = Float.parseFloat(numbers[j + 4]) + (isCommandRelative ? originalPenCoords.x : 0);
penCoords.y = Float.parseFloat(numbers[j + 5]) + (isCommandRelative ? originalPenCoords.y : 0);
Point newPoint = new Point(penCoords.x, penCoords.y, Point.COMMANDS.line);
leftHandle.subtract(newPoint);
newPoint.setLeftHandle(leftHandle);
points.add(newPoint);
// update originalPenCoords
originalPenCoords = newPoint.clone();
}
break;

case smoothCubicBezier:
originalPenCoords = points.get(points.size() - 1).clone();
for (int j = 0; j < numbers.length - 3; j += 4) {
// Reflect the left handle of the last control point.
Point leftHandle = points.get(points.size() - 1).getLeftHandle();
leftHandle.subtract(points.get(points.size() - 1));
points.get(points.size() - 1).setRightHandle(new Point(
-leftHandle.x,
-leftHandle.y
));
// Defines the handle for a new point.
penCoords.x = Float.parseFloat(numbers[j]) + (isCommandRelative ? originalPenCoords.x : 0);
penCoords.y = Float.parseFloat(numbers[j + 1]) + (isCommandRelative ? originalPenCoords.y : 0);
leftHandle = new Point(
penCoords.x,
penCoords.y
);
// Makes a new point.
penCoords.x = Float.parseFloat(numbers[j + 2]) + (isCommandRelative ? originalPenCoords.x : 0);
penCoords.y = Float.parseFloat(numbers[j + 3]) + (isCommandRelative ? originalPenCoords.y : 0);
Point newPoint = new Point(penCoords.x, penCoords.y, Point.COMMANDS.line);
leftHandle.subtract(newPoint);
newPoint.setLeftHandle(leftHandle);
points.add(newPoint);
// update originalPenCoords
originalPenCoords = newPoint.clone();
}
break;
}
}

Expand All @@ -293,37 +355,13 @@ public Point.COMMANDS svgToPointCommand(char svgCommand) {
return Point.COMMANDS.horizontal;
case 'v':
return Point.COMMANDS.vertical;
case 'c':
return Point.COMMANDS.cubicBezier;
case 's':
return Point.COMMANDS.smoothCubicBezier;
default:
return Point.COMMANDS.none;
}
}

/**
* Parses a floating point string, with decimals and negative values, to a <code>float</code>.
* Uses DecimalFormat.
*
* @param input The String to parse (ex. <code>"-100.54313"</code>
* @return The String, converted to a number (<code>Float</code>)
*/
private float parseFloat(String input) {
DecimalFormat df = new DecimalFormat();
try {
return Objects.requireNonNull(df.parse(input.trim())).floatValue();
} catch (Exception e) {
Log.e("Warning:", "There was an issue parsing a float. It's been replaced with 0, but here's the error:");
e.printStackTrace();
return 0;
}
}

/**
* Checks if a number (as a String) is able to be parsed from <code>parseFloat</code>, or in other words, if it is a float
*
* @param input The number, as a String, to check.
* @return Whether or not parseFloat can parse the number
*/
private boolean isFloat(String input) {
return !input.matches("[^\\d.]") && !input.equals("");
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.github.pastthepixels.freepaint;
package io.github.pastthepixels.freepaint.Graphics;

import android.annotation.SuppressLint;
import android.content.Context;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.github.pastthepixels.freepaint;
package io.github.pastthepixels.freepaint.Graphics;

import android.annotation.SuppressLint;
import android.content.Context;
Expand All @@ -23,6 +23,7 @@
import java.util.Objects;

import io.github.pastthepixels.freepaint.File.SVG;
import io.github.pastthepixels.freepaint.MainActivity;
import io.github.pastthepixels.freepaint.Tools.EraserTool;
import io.github.pastthepixels.freepaint.Tools.PaintTool;
import io.github.pastthepixels.freepaint.Tools.PanTool;
Expand Down
Loading

0 comments on commit d7668a4

Please sign in to comment.