1
0

add v1.1.4.516

Signed-off-by: Y7000p <xcl@xuegao-tzx.top>
This commit is contained in:
田梓萱 2022-05-16 14:19:26 +08:00
commit 9d2076b0be
58 changed files with 3212 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/upload.gradle
/gradle.properties
/build
/cer

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

Binary file not shown.

View File

@ -0,0 +1,2 @@
#Sun May 15 20:49:35 CST 2022
gradle.version=7.4

Binary file not shown.

BIN
.gradle/file-system.probe Normal file

Binary file not shown.

View File

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

1
.idea/.name Normal file
View File

@ -0,0 +1 @@
ImageTracer

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/compiler.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" />
</component>
</project>

6
.idea/copyright/2_0.xml Normal file
View File

@ -0,0 +1,6 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value="/*&#10; * Copyright 2022 田梓萱, xcl@xuegao-tzx.top&#10; *&#10; * Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10; * you may not use this file except in compliance with the License.&#10; * You may obtain a copy of the License at&#10; *&#10; * http://www.apache.org/licenses/LICENSE-2.0&#10; *&#10; * Unless required by applicable law or agreed to in writing, software&#10; * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10; * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10; * See the License for the specific language governing permissions and&#10; * limitations under the License.&#10; */" />
<option name="myName" value="开源2.0" />
</copyright>
</component>

19
.idea/gradle.xml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="delegatedBuild" value="true" />
<option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

View File

@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaDocConfiguration">
<GENERAL>
<MODE>UPDATE</MODE>
<OVERRIDDEN_METHODS>false</OVERRIDDEN_METHODS>
<SPLITTED_CLASS_NAME>true</SPLITTED_CLASS_NAME>
<LEVELS>
<LEVEL>METHOD</LEVEL>
<LEVEL>FIELD</LEVEL>
<LEVEL>TYPE</LEVEL>
</LEVELS>
<VISIBILITIES>
<VISIBILITY>PROTECTED</VISIBILITY>
<VISIBILITY>PUBLIC</VISIBILITY>
<VISIBILITY>DEFAULT</VISIBILITY>
</VISIBILITIES>
</GENERAL>
<TEMPLATES>
<CLASSES>
<CLASS>
<KEY>^.*(public|protected|private)*.+interface\s+\w+.*</KEY>
<VALUE>/**\n
* The interface ${name}.\n
&lt;#if element.typeParameters?has_content&gt; * \n
&lt;/#if&gt;&lt;#list element.typeParameters as typeParameter&gt; * @param &lt;${typeParameter.name}&gt; the type parameter\n
&lt;/#list&gt; */</VALUE>
</CLASS>
<CLASS>
<KEY>^.*(public|protected|private)*.+enum\s+\w+.*</KEY>
<VALUE>/**\n
* The enum ${name}.\n
*/</VALUE>
</CLASS>
<CLASS>
<KEY>^.*(public|protected|private)*.+class\s+\w+.*</KEY>
<VALUE>/**\n
* The type ${name}.\n
&lt;#if element.typeParameters?has_content&gt; * \n
&lt;/#if&gt;&lt;#list element.typeParameters as typeParameter&gt; * @param &lt;${typeParameter.name}&gt; the type parameter\n
&lt;/#list&gt; */</VALUE>
</CLASS>
<CLASS>
<KEY>.+</KEY>
<VALUE>/**\n
* The type ${name}.\n
*/</VALUE>
</CLASS>
</CLASSES>
<CONSTRUCTORS>
<CONSTRUCTOR>
<KEY>.+</KEY>
<VALUE>/**\n
* Instantiates a new ${name}.\n
&lt;#if element.parameterList.parameters?has_content&gt; *\n
&lt;/#if&gt;&lt;#list element.parameterList.parameters as parameter&gt; * @param ${parameter.name} the ${paramNames[parameter.name]}\n
&lt;/#list&gt;&lt;#if element.throwsList.referenceElements?has_content&gt; *\n
&lt;/#if&gt;&lt;#list element.throwsList.referenceElements as exception&gt; * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
&lt;/#list&gt; */</VALUE>
</CONSTRUCTOR>
</CONSTRUCTORS>
<METHODS>
<METHOD>
<KEY>^.*(public|protected|private)*\s*.*(\w(\s*&lt;.+&gt;)*)+\s+get\w+\s*\(.*\).+</KEY>
<VALUE>/**\n
* Gets ${partName}.\n
&lt;#if element.typeParameters?has_content&gt; * \n
&lt;/#if&gt;&lt;#list element.typeParameters as typeParameter&gt; * @param &lt;${typeParameter.name}&gt; the type parameter\n
&lt;/#list&gt;&lt;#if element.parameterList.parameters?has_content&gt; *\n
&lt;/#if&gt;&lt;#list element.parameterList.parameters as parameter&gt; * @param ${parameter.name} the ${paramNames[parameter.name]}\n
&lt;/#list&gt;&lt;#if isNotVoid&gt; *\n
* @return the ${partName}\n
&lt;/#if&gt;&lt;#if element.throwsList.referenceElements?has_content&gt; *\n
&lt;/#if&gt;&lt;#list element.throwsList.referenceElements as exception&gt; * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
&lt;/#list&gt; */</VALUE>
</METHOD>
<METHOD>
<KEY>^.*(public|protected|private)*\s*.*(void|\w(\s*&lt;.+&gt;)*)+\s+set\w+\s*\(.*\).+</KEY>
<VALUE>/**\n
* Sets ${partName}.\n
&lt;#if element.typeParameters?has_content&gt; * \n
&lt;/#if&gt;&lt;#list element.typeParameters as typeParameter&gt; * @param &lt;${typeParameter.name}&gt; the type parameter\n
&lt;/#list&gt;&lt;#if element.parameterList.parameters?has_content&gt; *\n
&lt;/#if&gt;&lt;#list element.parameterList.parameters as parameter&gt; * @param ${parameter.name} the ${paramNames[parameter.name]}\n
&lt;/#list&gt;&lt;#if isNotVoid&gt; *\n
* @return the ${partName}\n
&lt;/#if&gt;&lt;#if element.throwsList.referenceElements?has_content&gt; *\n
&lt;/#if&gt;&lt;#list element.throwsList.referenceElements as exception&gt; * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
&lt;/#list&gt; */</VALUE>
</METHOD>
<METHOD>
<KEY>^.*((public\s+static)|(static\s+public))\s+void\s+main\s*\(\s*String\s*(\[\s*\]|\.\.\.)\s+\w+\s*\).+</KEY>
<VALUE>/**\n
* The entry point of application.\n
&lt;#if element.parameterList.parameters?has_content&gt; *\n
&lt;/#if&gt; * @param ${element.parameterList.parameters[0].name} the input arguments\n
&lt;#if element.throwsList.referenceElements?has_content&gt; *\n
&lt;/#if&gt;&lt;#list element.throwsList.referenceElements as exception&gt; * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
&lt;/#list&gt; */</VALUE>
</METHOD>
<METHOD>
<KEY>.+</KEY>
<VALUE>/**\n
* ${name}&lt;#if isNotVoid&gt; ${return}&lt;/#if&gt;.\n
&lt;#if element.typeParameters?has_content&gt; * \n
&lt;/#if&gt;&lt;#list element.typeParameters as typeParameter&gt; * @param &lt;${typeParameter.name}&gt; the type parameter\n
&lt;/#list&gt;&lt;#if element.parameterList.parameters?has_content&gt; *\n
&lt;/#if&gt;&lt;#list element.parameterList.parameters as parameter&gt; * @param ${parameter.name} the ${paramNames[parameter.name]}\n
&lt;/#list&gt;&lt;#if isNotVoid&gt; *\n
* @return the ${return}\n
&lt;/#if&gt;&lt;#if element.throwsList.referenceElements?has_content&gt; *\n
&lt;/#if&gt;&lt;#list element.throwsList.referenceElements as exception&gt; * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
&lt;/#list&gt; */</VALUE>
</METHOD>
</METHODS>
<FIELDS>
<FIELD>
<KEY>^.*(public|protected|private)*.+static.*(\w\s\w)+.+</KEY>
<VALUE>/**\n
* The constant ${element.getName()}.\n
*/</VALUE>
</FIELD>
<FIELD>
<KEY>^.*(public|protected|private)*.*(\w\s\w)+.+</KEY>
<VALUE>/**\n
&lt;#if element.parent.isInterface()&gt; * The constant ${element.getName()}.\n
&lt;#else&gt; * The ${name}.\n
&lt;/#if&gt; */</VALUE>
</FIELD>
<FIELD>
<KEY>.+</KEY>
<VALUE>/**\n
&lt;#if element.parent.isEnum()&gt; *${name} ${typeName}.\n
&lt;#else&gt; * The ${name}.\n
&lt;/#if&gt;*/</VALUE>
</FIELD>
</FIELDS>
</TEMPLATES>
</component>
</project>

35
.idea/jarRepositories.xml Normal file
View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://mirrors.huaweicloud.com/repository/maven/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven2" />
<option name="name" value="maven2" />
<option name="url" value="https://developer.huawei.com/repo/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven3" />
<option name="name" value="maven3" />
<option name="url" value="https://repo.huaweicloud.com/repository/maven/" />
</remote-repository>
</component>
</project>

15
.idea/misc.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="SwUserDefinedSpecifications">
<option name="specTypeByUrl">
<map />
</option>
</component>
</project>

View File

@ -0,0 +1,34 @@
g java:S112:"FDefine and throw a dedicated exception instead of using a generic one.(»Œ<C2BB>ßûÿÿÿÿµÖŒ0
b java:S112K"FDefine and throw a dedicated exception instead of using a generic one.(ÝäâúµÖŒ0

java:S1319q"dThe type of "options" should be an interface such as "Map" rather than the implementation "HashMap".(«Š¾úùÿÿÿÿ8•ŒµÖŒ0
t
java:S1104"QMake width a static final constant or non-public and provide accessors if needed.(åáÛ‹ûÿÿÿÿ8žŒµÖŒ0
p
java:S1104"RMake height a static final constant or non-public and provide accessors if needed.(Á“Çú8ŸŒµÖŒ0
t
java:S1104»"QMake width a static final constant or non-public and provide accessors if needed.(åáÛ‹ûÿÿÿÿ8ŸŒµÖŒ0
p
java:S1104¾"RMake height a static final constant or non-public and provide accessors if needed.(Á“Çú8 ŒµÖŒ0
s
java:S1104Â"PMake data a static final constant or non-public and provide accessors if needed.(µ÷§Çøÿÿÿÿ8¡ŒµÖŒ0
n
java:S2095?"LUse try-with-resources or close this "BufferedWriter" in a "finally" clause.(îÞÀ‘ûÿÿÿÿ8¿ŽµÖŒ0
£
java:S1186/"€Add a nested comment explaining why this method is empty, throw an UnsupportedOperationException or complete the implementation.(æ—¶¾üÿÿÿÿ8ÍŽµÖŒ0
x
java:S1450*"VRemove the "rawdata" field and declare it as a local variable in the relevant methods.(ú é”úÿÿÿÿ8ÝŽµÖŒ0
?
java:S1118/"Hide this public constructor.(æ—¶¾üÿÿÿÿ8쎵֌0
^ java:S899="BDo something with the "boolean" value returned by "createNewFile".(úܲ¥<38>µÖŒ0
P
java:S1172E".Remove this unused method parameter "options".(ÖÚ‚Ùüÿÿÿÿ<38>µÖŒ0

java:S1130K"lRemove the declaration of thrown exception 'java.lang.Exception', as it cannot be thrown from method's body.(Ýäâú<38>µÖŒ0
] java:S125Z"<This block of commented-out lines of code should be removed.(ô¹ù·ýÿÿÿÿ<38>µÖŒ0
Z java:S106r"9Replace this use of System.out or System.err by a logger.(ܳš·üÿÿÿÿ<38>µÖŒ0
B
java:S1659"$Declare "height" on a separate line.(Á“Çú<38>µÖŒ0
B
java:S1659¾"$Declare "height" on a separate line.(Á“Çú8€<38>µÖŒ0

View File

@ -0,0 +1,46 @@
k java:S117]"QRename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.(•Ž…<C5BD>øÿÿÿÿ
\
java:S2184È"ECast one of the operands of this subtraction operation to a "double".(Äž¯í
c
java:S3973q"HUse indentation to denote the code conditionally executed by this "for".(Êóšâýÿÿÿÿ
U
java:S1118":Add a private constructor to hide the implicit public one.(çž<C3A7>ªûÿÿÿÿ
Q java:S125'"<This block of commented-out lines of code should be removed.(¥­ùÀ
m
java:S3776H"RRefactor this method to reduce its Cognitive Complexity from 29 to the 15 allowed.(ïÓ„æøÿÿÿÿ
U
java:S1659Z"?Declare "cd" and all following declarations on a separate line.(Ÿ<E28098><C5B8>
P java:S125e"<This block of commented-out lines of code should be removed.(ó<>É}
n
java:S3776¹"RRefactor this method to reduce its Cognitive Complexity from 36 to the 15 allowed.(µú—äýÿÿÿÿ
i
java:S3776í"RRefactor this method to reduce its Cognitive Complexity from 24 to the 15 allowed.(‹Žúª
=
java:S1659õ"&Declare "holepath" on a separate line.(É´ÓÖ
i
java:S3776¸"RRefactor this method to reduce its Cognitive Complexity from 22 to the 15 allowed.(<28>žÆë
V
java:S1659½"@Declare "pp2" and all following declarations on a separate line.(Æд
[
java:S1659¾"DDeclare "nextidx" and all following declarations on a separate line.(×£âÀ
H
java:S5261Ø"1Add explicit curly braces to avoid dangling else.(ôØÿ½
M
java:S5261ß"1Add explicit curly braces to avoid dangling else.(««ÒÀøÿÿÿÿ
I
java:S1135"2Complete the task associated to this TODO comment.(Œ¢Â¨
@
java:S1659<18>"$Declare "seqend" on a separate line.(™›‘žüÿÿÿÿ
<
java:S1659"&Declare "segtype2" on a separate line.(¿Ðìn
R java:S125"<This block of commented-out lines of code should be removed.(Ê¿…ë
W java:S125§"<This block of commented-out lines of code should be removed.(š™Ó’þÿÿÿÿ
I
java:S1135¥"2Complete the task associated to this TODO comment.(Œ¢Â¨
n
java:S3776´"RRefactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.(òƒ¨‚úÿÿÿÿ
V
java:S1659¾"?Declare "py" and all following declarations on a separate line.(™ïØ·
V
java:S1659ê"?Declare "t1" and all following declarations on a separate line.(Ü¢þ•

View File

@ -0,0 +1,5 @@
J
java:S2699"-Add at least one assertion to this test case.(þ–’ê8®œàÁŒ0
¡
java:S3577Rename class "Test" to match the regular expression: '^((Test|IT)[a-zA-Z0-9_]+|[A-Z][a-zA-Z0-9_]*(Test|Tests|TestCase|IT|ITCase))$'(Óµ¨œ ®¾Œ0

View File

@ -0,0 +1,24 @@
k
;src/main/java/com/xcl/imagetracer_mod/VectorizingUtils.java,7\6\7694485864010b12895922b15f2a2725fba0800a
_
/src/test/java/com/xcl/imagetracer_mod/Test.java,e\1\e10c18fde270132583fe384e4bd43a528f6d5e3d
c
3src/main/java/com/xcl/imagetracer_mod/SVGUtils.java,1\5\1586d759e116a7db0964977c1b73b0ffb0d15141
h
8src/main/java/com/xcl/imagetracer_mod/SelectiveBlur.java,c\0\c0e3efd21e2289b1fc9bd254fb2d359b2be6d8aa
f
6src/main/java/com/xcl/imagetracer_mod/ImageTracer.java,0\a\0ab6f53a3ca221a03ba699f735c59475a23a261b
?
settings.gradle,0\5\05efc8b1657769a27696d478ded1e95f38737233
<
build.gradle,f\0\f07866736216be0ee2aba49e392191aeae700a35
A
gradle.properties,2\a\2afbb999f001938c88fa43fc2ef52abf0f8213e4
9
README.md,8\e\8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d
=
upload.gradle,9\2\92bb4b9034d01b5517aed00be842828de7184f6e
:
.gitignore,a\5\a5cc2925ca8258af241be7e5b0381edf30266302

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

148
README.md Normal file
View File

@ -0,0 +1,148 @@
# ImageTracerJava
ImageTracerJava(A library that can transfer pictures such as PNG to SVG)<br>
ImageTracerJava(一个可以把png等图片转svg的开源Java库)
## How to Use:
### Including in Java projects
Add **ImageTracer.jar** to your build path
**ImageTracer.jar** 添加到构建路径
### 安装教程
**Add the mavenCentral repository under the project's build.gradle**
**在 Project 的 build.gradle 下添加 mavenCentral 仓库**
```groovy
repositories {
maven {
url 'https://repo.huaweicloud.com/repository/maven/'
}
maven {
url 'https://developer.huawei.com/repo/'
}
mavenCentral()
}
```
**Add ImageTracerJava dependency under Module's build.gradle**
**在 Module 的 build.gradle 下添加 ImageTracerJava 依赖**
```groovy
implementation 'top.xuegao-tzx:ImageTracerJava:1.1.4.516'
```
then use the static methods:
然后使用静态方法:
```java
import com.xcl.imagetracer_mod.ImageTracer;
...
ImageTracer.saveString(
"output.svg" ,
ImageTracer.imageToSVG("input.jpg",null,null)
);
```
With options and palette
带有选项和调色板的方法:
```java
// Options
HashMap<String,Float> options = new HashMap<String,Float>();
// Tracing
options.put("ltres",1f);
options.put("qtres",1f);
options.put("pathomit",8f);
// Color quantization
options.put("colorsampling",1f); // 1f means true ; 0f means false: starting with generated palette
options.put("numberofcolors",16f);
options.put("mincolorratio",0.02f);
options.put("colorquantcycles",3f);
// SVG rendering
options.put("scale",1f);
options.put("roundcoords",1f); // 1f means rounded to 1 decimal places, like 7.3 ; 3f means rounded to 3 places, like 7.356 ; etc.
options.put("lcpr",0f);
options.put("qcpr",0f);
options.put("desc",1f); // 1f means true ; 0f means false: SVG descriptions deactivated
options.put("viewbox",0f); // 1f means true ; 0f means false: fixed width and height
// Selective Gauss Blur
options.put("blurradius",0f); // 0f means deactivated; 1f .. 5f : blur with this radius
options.put("blurdelta",20f); // smaller than this RGB difference will be blurred
// Palette
// This is an example of a grayscale palette
// please note that signed byte values [ -128 .. 127 ] will be converted to [ 0 .. 255 ] in the getsvgstring function
// the two number '8' below,you can change it to any number between 4 and 16,you need to change this by myself,so that you can make the SVG more clear!
byte[][] palette = new byte[8][4];
for(int colorcnt=0; colorcnt < 8; colorcnt++){
palette[colorcnt][0] = (byte)( -128 + colorcnt * 32); // R
palette[colorcnt][1] = (byte)( -128 + colorcnt * 32); // G
palette[colorcnt][2] = (byte)( -128 + colorcnt * 32); // B
palette[colorcnt][3] = (byte)127; // A
}
ImageTracer.saveString(
"output.svg" ,
ImageTracer.imageToSVG("input.jpg",options,palette)
);
```
### Deterministic output
See [options for deterministic tracing](https://github.com/jankovicsandras/imagetracerjava/blob/master/deterministic.md)
### Main Functions
### 主要功能
|Function name|Arguments|Returns|
|-------------|---------|-------|
|```imageToSVG```|```String filename, HashMap<String,Float> options /*can be null*/, byte [][] palette /*can be null*/```|```String /*SVG content*/```|
|```imageToSVG```|```BufferedImage image, HashMap<String,Float> options /*can be null*/, byte [][] palette /*can be null*/```|```String /*SVG content*/```|
|```imagedataToSVG```|```ImageData imgd, HashMap<String,Float> options /*can be null*/, byte [][] palette /*can be null*/```|```String /*SVG content*/```|
|```imageToTracedata```|```String filename, HashMap<String,Float> options /*can be null*/, byte [][] palette /*can be null*/```|```IndexedImage /*read the source for details*/```|
|```imageToTracedata```|```BufferedImage image, HashMap<String,Float> options /*can be null*/, byte [][] palette /*can be null*/```|```IndexedImage /*read the source for details*/```|
|```imagedataToTracedata```|```ImageData imgd, HashMap<String,Float> options /*can be null*/, byte [][] palette /*can be null*/```|```IndexedImage /*read the source for details*/```|
#### Helper Functions
|Function name|Arguments|Returns|
|-------------|---------|-------|
|```saveString```|```String filename, String str```|```void```|
|```loadImageData```|```String filename```|```ImageData /*read the source for details*/```|
|```loadImageData```|```BufferedImage image```|```ImageData /*read the source for details*/```|
```ImageData``` is similar to [ImageData](https://developer.mozilla.org/en-US/docs/Web/API/ImageData) here.
There are more functions for advanced users, read the source if you are interested. :)
### Options
### 配置选项
|Option name|Default value|Meaning|
|-----------|-------------|-------|
|```ltres```|```1f```|Error treshold for straight lines.|
|```qtres```|```1f```|Error treshold for quadratic splines.|
|```pathomit```|```8f```|Edge node paths shorter than this will be discarded for noise reduction.|
|```colorsampling```|```1f```|Enable or disable color sampling. 1f is on, 0f is off.|
|```numberofcolors```|```16f```|Number of colors to use on palette if pal object is not defined.|
|```mincolorratio```|```0.02f```|Color quantization will randomize a color if fewer pixels than (total pixels*mincolorratio) has it.|
|```colorquantcycles```|```3f```|Color quantization will be repeated this many times.|
|```blurradius```|```0f```|Set this to 1f..5f for selective Gaussian blur preprocessing.|
|```blurdelta```|```20f```|RGBA delta treshold for selective Gaussian blur preprocessing.|
|```scale```|```1f```|Every coordinate will be multiplied with this, to scale the SVG.|
|```roundcoords```|```1f```|rounding coordinates to a given decimal place. 1f means rounded to 1 decimal place like 7.3 ; 3f means rounded to 3 places, like 7.356|
|```viewbox```|```0f```|Enable or disable SVG viewBox. 1f is on, 0f is off.|
|```desc```|```1f```|Enable or disable SVG descriptions. 1f is on, 0f is off.|
|```lcpr```|```0f```|Straight line control point radius, if this is greater than zero, small circles will be drawn in the SVG. Do not use this for big/complex images.|
|```qcpr```|```0f```|Quadratic spline control point radius, if this is greater than zero, small circles and lines will be drawn in the SVG. Do not use this for big/complex images.|
### Process overview
See [Process overview and Ideas for improvement](https://github.com/jankovicsandras/imagetracerjava/blob/master/process_overview.md)
### License
### 许可证
Modfiy Author田梓萱<br>
ImageTracerJava 在 [Apache 2.0 License](LICENSE)下获得许可

40
build.gradle Normal file
View File

@ -0,0 +1,40 @@
apply plugin: 'java'
apply from: './upload.gradle' // Maven之后需要删除该行代码
group = 'top.xuegao-tzx'
version= '1.1.4.516'
repositories {
maven {
url 'https://mirrors.huaweicloud.com/repository/maven/'
allowInsecureProtocol = true
}
maven {
url 'https://developer.huawei.com/repo/'
allowInsecureProtocol = true
}
maven {
url 'https://repo.huaweicloud.com/repository/maven/'
allowInsecureProtocol = true
}
mavenCentral()
}
jar {
enabled = true
}
javadoc {
options.encoding = "UTF-8"
failOnError = false
}
dependencies {
implementation 'junit:junit:4.13.1'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
test {
useJUnitPlatform()
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

234
gradlew vendored Normal file
View File

@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

Binary file not shown.

2
settings.gradle Normal file
View File

@ -0,0 +1,2 @@
rootProject.name = 'ImageTracer'

View File

@ -0,0 +1,210 @@
/*
* Copyright 2022 田梓萱, xcl@xuegao-tzx.top
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.xcl.imagetracer_mod;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.HashMap;
////////////////////////////////////////////////////////////////////////////////////////
// //
// Open source address of the project:https://github.com/xuegao-tzx/ImageTracerJava //
// 本项目开源地址:https://gitee.com/xuegao-tzx/ImageTracerJava //
// 作者:田梓萱(XCL) //
// //
////////////////////////////////////////////////////////////////////////////////////////
/**
* The type Image tracer.
*/
public class ImageTracer {
/**
* The Versionnumber.
*/
static String versionnumber = "1.1.4.516";
private static int[] rawdata;
/**
* Instantiates a new Image tracer.
*/
public ImageTracer() {
}
/**
* Save string.
*
* @param filename the filename
* @param str the str
* @throws Exception the exception
*/
// Saving a String as a file
public static void saveString(String filename, String str) throws Exception {
File file = new File(filename);
// if file doesnt exists, then create it
if (!file.exists()) file.createNewFile();
FileWriter fw = new FileWriter(file.getAbsoluteFile());
BufferedWriter bw = new BufferedWriter(fw);
bw.write(str);
bw.close();
}
// Loading a file to ImageData, ARGB byte order
private static ImageData loadImageData(String filename, HashMap<String, Float> options) throws Exception {
BufferedImage image = ImageIO.read(new File(filename));
return ImageTracer.loadImageData(image);
}
private static ImageData loadImageData(BufferedImage image) throws Exception {
int width = image.getWidth();
int height = image.getHeight();
ImageTracer.rawdata = image.getRGB(0, 0, width, height, null, 0, width);
byte[] data = new byte[ImageTracer.rawdata.length * 4];
for (int i = 0; i < ImageTracer.rawdata.length; i++) {
data[(i * 4) + 3] = ImageTracer.bytetrans((byte) (ImageTracer.rawdata[i] >>> 24));
data[i * 4] = ImageTracer.bytetrans((byte) (ImageTracer.rawdata[i] >>> 16));
data[(i * 4) + 1] = ImageTracer.bytetrans((byte) (ImageTracer.rawdata[i] >>> 8));
data[(i * 4) + 2] = ImageTracer.bytetrans((byte) (ImageTracer.rawdata[i]));
}
return new ImageData(width, height, data);
}
// The bitshift method in loadImageData creates signed bytes where -1 -> 255 unsigned ; -128 -> 128 unsigned ;
// 127 -> 127 unsigned ; 0 -> 0 unsigned ; These will be converted to -128 (representing 0 unsigned) ...
// 127 (representing 255 unsigned) and tosvgcolorstr will add +128 to create RGB values 0..255
private static byte bytetrans(byte b) {
if (b < 0) return (byte) (b + 128);
else return (byte) (b - 128);
}
/**
* Image to svg string.
*
* @param filename the filename
* @param options the options
* @param palette the palette
* @return the string
* @throws Exception the exception
*/
////////////////////////////////////////////////////////////
//
// User friendly functions
//
////////////////////////////////////////////////////////////
// Loading an image from a file, tracing when loaded, then returning the SVG String
public static String imageToSVG(String filename, HashMap<String, Float> options, byte[][] palette) throws Exception {
System.out.println("自定义配置:" + options.toString());
ImageData imgd = ImageTracer.loadImageData(filename, options);
return ImageTracer.imagedataToSVG(imgd, options, palette);
}// End of imageToSVG()
// Tracing ImageData, then returning the SVG String
private static String imagedataToSVG(ImageData imgd, HashMap<String, Float> options, byte[][] palette) {
IndexedImage ii = ImageTracer.imagedataToTracedata(imgd, options, palette);
return SVGUtils.getsvgstring(ii, options);
}// End of imagedataToSVG()
// Tracing ImageData, then returning IndexedImage with tracedata in layers
private static IndexedImage imagedataToTracedata(ImageData imgd, HashMap<String, Float> options, byte[][] palette) {
// 1. Color quantization
IndexedImage ii = VectorizingUtils.colorquantization(imgd, palette, options);
// 2. Layer separation and edge detection
int[][][] rawlayers = VectorizingUtils.layering(ii);
// 3. Batch pathscan
ArrayList<ArrayList<ArrayList<Integer[]>>> bps = VectorizingUtils.batchpathscan(rawlayers, (int) (Math.floor(options.get("pathomit"))));
// 4. Batch interpollation
ArrayList<ArrayList<ArrayList<Double[]>>> bis = VectorizingUtils.batchinternodes(bps);
// 5. Batch tracing
ii.layers = VectorizingUtils.batchtracelayers(bis, options.get("ltres"), options.get("qtres"));
return ii;
}// End of imagedataToTracedata()
/**
* The type Indexed image.
*/
// Container for the color-indexed image before and tracedata after vectorizing
public static class IndexedImage {
/**
* The Width.
*/
public int width, /**
* The Height.
*/
height;
/**
* The Array.
*/
int[][] array; // array[x][y] of palette colors
/**
* The Palette.
*/
byte[][] palette;// array[palettelength][4] RGBA color palette
/**
* The Layers.
*/
ArrayList<ArrayList<ArrayList<Double[]>>> layers;// tracedata
/**
* Instantiates a new Indexed image.
*
* @param marray the marray
* @param mpalette the mpalette
*/
IndexedImage(int[][] marray, byte[][] mpalette) {
array = marray;
palette = mpalette;
width = marray[0].length - 2;
height = marray.length - 2;// Color quantization adds +2 to the original width and height
}
}
/**
* The type Image data.
*/
// https://developer.mozilla.org/en-US/docs/Web/API/ImageData
public static class ImageData {
/**
* The Width.
*/
public int width, /**
* The Height.
*/
height;
/**
* The Data.
*/
public byte[] data; // raw byte data: R G B A R G B A ...
/**
* Instantiates a new Image data.
*
* @param mwidth the mwidth
* @param mheight the mheight
* @param mdata the mdata
*/
ImageData(int mwidth, int mheight, byte[] mdata) {
width = mwidth;
height = mheight;
data = mdata;
}
}
}// End of ImageTracer class

View File

@ -0,0 +1,151 @@
/*
* Copyright 2022 田梓萱, xcl@xuegao-tzx.top
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.xcl.imagetracer_mod;
import com.xcl.imagetracer_mod.ImageTracer.IndexedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.TreeMap;
/**
* The type Svg utils.
*/
class SVGUtils {
////////////////////////////////////////////////////////////
//
// SVG Drawing functions
//
////////////////////////////////////////////////////////////
private static float roundtodec(float val, float places) {
return (float) (Math.round(val * Math.pow(10, places)) / Math.pow(10, places));
}
// Getting SVG path element string from a traced path
private static void svgpathstring(StringBuilder sb, String desc, ArrayList<Double[]> segments, String colorstr, HashMap<String, Float> options) {
float scale = options.get("scale");
float lcpr = options.get("lcpr");
float qcpr = options.get("qcpr");
float roundcoords = (float) Math.floor(options.get("roundcoords"));
// Path
sb.append("<path ").append(desc).append(colorstr).append("d=\"").append("M ").append(segments.get(0)[1] * scale).append(" ").append(segments.get(0)[2] * scale).append(" ");
// End of roundcoords check
if (roundcoords == -1) for (int pcnt = 0; pcnt < segments.size(); pcnt++)
if (segments.get(pcnt)[0] == 1.0)
sb.append("L ").append(segments.get(pcnt)[3] * scale).append(" ").append(segments.get(pcnt)[4] * scale).append(" ");
else
sb.append("Q ").append(segments.get(pcnt)[3] * scale).append(" ").append(segments.get(pcnt)[4] * scale).append(" ").append(segments.get(pcnt)[5] * scale).append(" ").append(segments.get(pcnt)[6] * scale).append(" ");
else
for (int pcnt = 0; pcnt < segments.size(); pcnt++)
if (segments.get(pcnt)[0] == 1.0)
sb.append("L ").append(SVGUtils.roundtodec((float) (segments.get(pcnt)[3] * scale), roundcoords)).append(" ")
.append(SVGUtils.roundtodec((float) (segments.get(pcnt)[4] * scale), roundcoords)).append(" ");
else
sb.append("Q ").append(SVGUtils.roundtodec((float) (segments.get(pcnt)[3] * scale), roundcoords)).append(" ")
.append(SVGUtils.roundtodec((float) (segments.get(pcnt)[4] * scale), roundcoords)).append(" ")
.append(SVGUtils.roundtodec((float) (segments.get(pcnt)[5] * scale), roundcoords)).append(" ")
.append(SVGUtils.roundtodec((float) (segments.get(pcnt)[6] * scale), roundcoords)).append(" ");
sb.append("Z\" />");
// Rendering control points
for (int pcnt = 0; pcnt < segments.size(); pcnt++) {
if ((lcpr > 0) && (segments.get(pcnt)[0] == 1.0))
sb.append("<circle cx=\"").append(segments.get(pcnt)[3] * scale).append("\" cy=\"").append(segments.get(pcnt)[4] * scale).append("\" r=\"").append(lcpr).append("\" fill=\"white\" stroke-width=\"").append(lcpr * 0.2).append("\" stroke=\"black\" />");
if ((qcpr > 0) && (segments.get(pcnt)[0] == 2.0)) {
sb.append("<circle cx=\"").append(segments.get(pcnt)[3] * scale).append("\" cy=\"").append(segments.get(pcnt)[4] * scale).append("\" r=\"").append(qcpr).append("\" fill=\"cyan\" stroke-width=\"").append(qcpr * 0.2).append("\" stroke=\"black\" />");
sb.append("<circle cx=\"").append(segments.get(pcnt)[5] * scale).append("\" cy=\"").append(segments.get(pcnt)[6] * scale).append("\" r=\"").append(qcpr).append("\" fill=\"white\" stroke-width=\"").append(qcpr * 0.2).append("\" stroke=\"black\" />");
sb.append("<line x1=\"").append(segments.get(pcnt)[1] * scale).append("\" y1=\"").append(segments.get(pcnt)[2] * scale).append("\" x2=\"").append(segments.get(pcnt)[3] * scale).append("\" y2=\"").append(segments.get(pcnt)[4] * scale).append("\" stroke-width=\"").append(qcpr * 0.2).append("\" stroke=\"cyan\" />");
sb.append("<line x1=\"").append(segments.get(pcnt)[3] * scale).append("\" y1=\"").append(segments.get(pcnt)[4] * scale).append("\" x2=\"").append(segments.get(pcnt)[5] * scale).append("\" y2=\"").append(segments.get(pcnt)[6] * scale).append("\" stroke-width=\"").append(qcpr * 0.2).append("\" stroke=\"cyan\" />");
}// End of quadratic control points
}
}// End of svgpathstring()
/**
* Gets .
*
* @param ii the ii
* @param options the options
* @return the
*/
// Converting tracedata to an SVG string, paths are drawn according to a Z-index
// the optional lcpr and qcpr are linear and quadratic control point radiuses
static String getsvgstring(IndexedImage ii, HashMap<String, Float> options) {
// SVG start
int w = (int) (ii.width * options.get("scale"));
int h = (int) (ii.height * options.get("scale"));
String viewboxorviewport = options.get("viewbox") != 0 ? "viewBox=\"0 0 " + w + " " + h + "\" " : "width=\"" + w + "\" height=\"" + h + "\" ";
StringBuilder svgstr = new StringBuilder("<svg " + viewboxorviewport + "version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" ");
if (options.get("desc") != 0)
svgstr.append("desc=\"Created with ImageTracerMod.java version " + ImageTracer.versionnumber + " Mod By XCL .\"");
svgstr.append(">");
// creating Z-index
TreeMap<Double, Integer[]> zindex = new TreeMap<>();
double label;
// Layer loop
// Path loop
// End of layer loop
for (int k = 0; k < ii.layers.size(); k++)
for (int pcnt = 0; pcnt < ii.layers.get(k).size(); pcnt++) {
// Label (Z-index key) is the startpoint of the path, linearized
label = (ii.layers.get(k).get(pcnt).get(0)[2] * w) + ii.layers.get(k).get(pcnt).get(0)[1];
// Creating new list if required
if (!zindex.containsKey(label)) zindex.put(label, new Integer[2]);
// Adding layer and path number to list
zindex.get(label)[0] = new Integer(k);
zindex.get(label)[1] = new Integer(pcnt);
}// End of path loop
// Sorting Z-index is not required, TreeMap is sorted automatically
// Drawing
// Z-index loop
String thisdesc = "";
for (Entry<Double, Integer[]> entry : zindex.entrySet()) {
if (options.get("desc") != 0)
thisdesc = "desc=\"l " + entry.getValue()[0] + " p " + entry.getValue()[1] + "\" ";
else thisdesc = "";
SVGUtils.svgpathstring(svgstr,
thisdesc,
ii.layers.get(entry.getValue()[0]).get(entry.getValue()[1]),
SVGUtils.tosvgcolorstr(ii.palette[entry.getValue()[0]]),
options);
}
// SVG End
svgstr.append("</svg>");
return svgstr.toString();
}// End of getsvgstring()
private static String tosvgcolorstr(byte[] c) {
return "fill=\"rgb(" + (c[0] + 128) + "," + (c[1] + 128) + "," + (c[2] + 128) + ")\" stroke=\"rgb(" + (c[0] + 128) + "," + (c[1] + 128) + "," + (c[2] + 128) + ")\" stroke-width=\"1\" opacity=\"" + ((c[3] + 128) / 255.0) + "\" ";
}
}

View File

@ -0,0 +1,137 @@
/*
* Copyright 2022 田梓萱, xcl@xuegao-tzx.top
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.xcl.imagetracer_mod;
import com.xcl.imagetracer_mod.ImageTracer.ImageData;
/**
* The type Selective blur.
*/
class SelectiveBlur {
// Gaussian kernels for blur
private static final double[][] gks = {{0.27901, 0.44198, 0.27901}, {0.135336, 0.228569, 0.272192, 0.228569, 0.135336}, {0.086776, 0.136394, 0.178908, 0.195843, 0.178908, 0.136394, 0.086776},
{0.063327, 0.093095, 0.122589, 0.144599, 0.152781, 0.144599, 0.122589, 0.093095, 0.063327}, {0.049692, 0.069304, 0.089767, 0.107988, 0.120651, 0.125194, 0.120651, 0.107988, 0.089767, 0.069304, 0.049692}};
/**
* Blur image data.
*
* @param imgd the imgd
* @param rad the rad
* @param del the del
* @return the image data
*/
// Selective Gaussian blur for preprocessing
static ImageData blur(ImageData imgd, float rad, float del) {
int i, j, k, d, idx;
double racc, gacc, bacc, aacc, wacc;
ImageData imgd2 = new ImageData(imgd.width, imgd.height, new byte[imgd.width * imgd.height * 4]);
// radius and delta limits, this kernel
int radius = (int) Math.floor(rad);
if (radius < 1) return imgd;
if (radius > 5) radius = 5;
int delta = (int) Math.abs(del);
if (delta > 1024) delta = 1024;
double[] thisgk = SelectiveBlur.gks[radius - 1];
// loop through all pixels, horizontal blur
// End of horizontal blur
for (j = 0; j < imgd.height; j++)
for (i = 0; i < imgd.width; i++) {
racc = 0;
gacc = 0;
bacc = 0;
aacc = 0;
wacc = 0;
// gauss kernel loop
// add weighted color values
for (k = -radius; k < (radius + 1); k++)
if (((i + k) > 0) && ((i + k) < imgd.width)) {
idx = ((j * imgd.width) + i + k) * 4;
racc += imgd.data[idx] * thisgk[k + radius];
gacc += imgd.data[idx + 1] * thisgk[k + radius];
bacc += imgd.data[idx + 2] * thisgk[k + radius];
aacc += imgd.data[idx + 3] * thisgk[k + radius];
wacc += thisgk[k + radius];
}
// The new pixel
idx = ((j * imgd.width) + i) * 4;
imgd2.data[idx] = (byte) Math.floor(racc / wacc);
imgd2.data[idx + 1] = (byte) Math.floor(gacc / wacc);
imgd2.data[idx + 2] = (byte) Math.floor(bacc / wacc);
imgd2.data[idx + 3] = (byte) Math.floor(aacc / wacc);
}// End of width loop
// copying the half blurred imgd2
byte[] himgd = imgd2.data.clone();
// loop through all pixels, vertical blur
// End of vertical blur
for (j = 0; j < imgd.height; j++)
for (i = 0; i < imgd.width; i++) {
racc = 0;
gacc = 0;
bacc = 0;
aacc = 0;
wacc = 0;
// gauss kernel loop
// add weighted color values
for (k = -radius; k < (radius + 1); k++)
if (((j + k) > 0) && ((j + k) < imgd.height)) {
idx = (((j + k) * imgd.width) + i) * 4;
racc += himgd[idx] * thisgk[k + radius];
gacc += himgd[idx + 1] * thisgk[k + radius];
bacc += himgd[idx + 2] * thisgk[k + radius];
aacc += himgd[idx + 3] * thisgk[k + radius];
wacc += thisgk[k + radius];
}
// The new pixel
idx = ((j * imgd.width) + i) * 4;
imgd2.data[idx] = (byte) Math.floor(racc / wacc);
imgd2.data[idx + 1] = (byte) Math.floor(gacc / wacc);
imgd2.data[idx + 2] = (byte) Math.floor(bacc / wacc);
imgd2.data[idx + 3] = (byte) Math.floor(aacc / wacc);
}// End of width loop
// Selective blur: loop through all pixels
// End of Selective blur
for (j = 0; j < imgd.height; j++)
for (i = 0; i < imgd.width; i++) {
idx = ((j * imgd.width) + i) * 4;
// d is the difference between the blurred and the original pixel
d = Math.abs(imgd2.data[idx] - imgd.data[idx]) + Math.abs(imgd2.data[idx + 1] - imgd.data[idx + 1]) +
Math.abs(imgd2.data[idx + 2] - imgd.data[idx + 2]) + Math.abs(imgd2.data[idx + 3] - imgd.data[idx + 3]);
// selective blur: if d>delta, put the original pixel back
if (d > delta) {
imgd2.data[idx] = imgd.data[idx];
imgd2.data[idx + 1] = imgd.data[idx + 1];
imgd2.data[idx + 2] = imgd.data[idx + 2];
imgd2.data[idx + 3] = imgd.data[idx + 3];
}
}
return imgd2;
}// End of blur()
}

View File

@ -0,0 +1,567 @@
/*
* Copyright 2022 田梓萱, xcl@xuegao-tzx.top
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.xcl.imagetracer_mod;
import com.xcl.imagetracer_mod.ImageTracer.ImageData;
import com.xcl.imagetracer_mod.ImageTracer.IndexedImage;
import java.util.ArrayList;
import java.util.HashMap;
/**
* The type Vectorizing utils.
*/
class VectorizingUtils {
////////////////////////////////////////////////////////////
//
// Vectorizing functions
//
////////////////////////////////////////////////////////////
// Lookup tables for pathscan
private static final byte[] pathscan_dir_lookup = {0, 0, 3, 0, 1, 0, 3, 0, 0, 3, 3, 1, 0, 3, 0, 0};
private static final boolean[] pathscan_holepath_lookup = {false, false, false, false, false, false, false, true, false, false, false, true, false, true, true, false};
// pathscan_combined_lookup[ arr[py][px] ][ dir ] = [nextarrpypx, nextdir, deltapx, deltapy];
private static final byte[][][] pathscan_combined_lookup = {
{{-1, -1, -1, -1}, {-1, -1, -1, -1}, {-1, -1, -1, -1}, {-1, -1, -1, -1}},// arr[py][px]==0 is invalid
{{0, 1, 0, -1}, {-1, -1, -1, -1}, {-1, -1, -1, -1}, {0, 2, -1, 0}},
{{-1, -1, -1, -1}, {-1, -1, -1, -1}, {0, 1, 0, -1}, {0, 0, 1, 0}},
{{0, 0, 1, 0}, {-1, -1, -1, -1}, {0, 2, -1, 0}, {-1, -1, -1, -1}},
{{-1, -1, -1, -1}, {0, 0, 1, 0}, {0, 3, 0, 1}, {-1, -1, -1, -1}},
{{13, 3, 0, 1}, {13, 2, -1, 0}, {7, 1, 0, -1}, {7, 0, 1, 0}},
{{-1, -1, -1, -1}, {0, 1, 0, -1}, {-1, -1, -1, -1}, {0, 3, 0, 1}},
{{0, 3, 0, 1}, {0, 2, -1, 0}, {-1, -1, -1, -1}, {-1, -1, -1, -1}},
{{0, 3, 0, 1}, {0, 2, -1, 0}, {-1, -1, -1, -1}, {-1, -1, -1, -1}},
{{-1, -1, -1, -1}, {0, 1, 0, -1}, {-1, -1, -1, -1}, {0, 3, 0, 1}},
{{11, 1, 0, -1}, {14, 0, 1, 0}, {14, 3, 0, 1}, {11, 2, -1, 0}},
{{-1, -1, -1, -1}, {0, 0, 1, 0}, {0, 3, 0, 1}, {-1, -1, -1, -1}},
{{0, 0, 1, 0}, {-1, -1, -1, -1}, {0, 2, -1, 0}, {-1, -1, -1, -1}},
{{-1, -1, -1, -1}, {-1, -1, -1, -1}, {0, 1, 0, -1}, {0, 0, 1, 0}},
{{0, 1, 0, -1}, {-1, -1, -1, -1}, {-1, -1, -1, -1}, {0, 2, -1, 0}},
{{-1, -1, -1, -1}, {-1, -1, -1, -1}, {-1, -1, -1, -1}, {-1, -1, -1, -1}}// arr[py][px]==15 is invalid
};
/**
* Colorquantization indexed image.
*
* @param imgd the imgd
* @param palette the palette
* @param options the options
* @return the indexed image
*/
// 1. Color quantization repeated "cycles" times, based on K-means clustering
// https://en.wikipedia.org/wiki/Color_quantization https://en.wikipedia.org/wiki/K-means_clustering
static IndexedImage colorquantization(ImageData imgd, byte[][] palette, HashMap<String, Float> options) {
// Selective Gaussian blur preprocessing
if (options.get("blurradius") > 0)
imgd = SelectiveBlur.blur(imgd, options.get("blurradius"), options.get("blurdelta"));
int cycles = (int) Math.floor(options.get("colorquantcycles"));
// Creating indexed color array arr which has a boundary filled with -1 in every direction
int[][] arr = new int[imgd.height + 2][imgd.width + 2];
for (int j = 0; j < (imgd.height + 2); j++) {
arr[j][0] = -1;
arr[j][imgd.width + 1] = -1;
}
for (int i = 0; i < (imgd.width + 2); i++) {
arr[0][i] = -1;
arr[imgd.height + 1][i] = -1;
}
int idx = 0, cd, cdl, ci, c1, c2, c3, c4;
byte[][] original_palette_backup = palette;
long[][] paletteacc = new long[palette.length][5];
// Repeat clustering step "cycles" times
for (int cnt = 0; cnt < cycles; cnt++) {
// Average colors from the second iteration
// averaging paletteacc for palette
//float ratio;
// averaging
//ratio = (float)( (double)(paletteacc[k][4]) / (double)(imgd.width*imgd.height) );
/*// Randomizing a color, if there are too few pixels and there will be a new cycle
if( (ratio<minratio) && (cnt<(cycles-1)) ){
palette[k][0] = (byte) (-128+Math.floor(Math.random()*255));
palette[k][1] = (byte) (-128+Math.floor(Math.random()*255));
palette[k][2] = (byte) (-128+Math.floor(Math.random()*255));
palette[k][3] = (byte) (-128+Math.floor(Math.random()*255));
}*/
// End of palette loop
// End of Average colors from the second iteration
if (cnt > 0) for (int k = 0; k < palette.length; k++)
if (paletteacc[k][3] > 0) {
palette[k][0] = (byte) (-128 + (paletteacc[k][0] / paletteacc[k][4]));
palette[k][1] = (byte) (-128 + (paletteacc[k][1] / paletteacc[k][4]));
palette[k][2] = (byte) (-128 + (paletteacc[k][2] / paletteacc[k][4]));
palette[k][3] = (byte) (-128 + (paletteacc[k][3] / paletteacc[k][4]));
}
// Reseting palette accumulator for averaging
for (int i = 0; i < palette.length; i++) {
paletteacc[i][0] = 0;
paletteacc[i][1] = 0;
paletteacc[i][2] = 0;
paletteacc[i][3] = 0;
paletteacc[i][4] = 0;
}
// loop through all pixels
// End of j loop
for (int j = 0; j < imgd.height; j++)
for (int i = 0; i < imgd.width; i++) {
idx = ((j * imgd.width) + i) * 4;
// find closest color from original_palette_backup by measuring (rectilinear)
// color distance between this pixel and all palette colors
cdl = 256 + 256 + 256 + 256;
ci = 0;
for (int k = 0; k < original_palette_backup.length; k++) {
// In my experience, https://en.wikipedia.org/wiki/Rectilinear_distance works better than https://en.wikipedia.org/wiki/Euclidean_distance
c1 = Math.abs(original_palette_backup[k][0] - imgd.data[idx]);
c2 = Math.abs(original_palette_backup[k][1] - imgd.data[idx + 1]);
c3 = Math.abs(original_palette_backup[k][2] - imgd.data[idx + 2]);
c4 = Math.abs(original_palette_backup[k][3] - imgd.data[idx + 3]);
cd = c1 + c2 + c3 + (c4 * 4); // weighted alpha seems to help images with transparency
// Remember this color if this is the closest yet
if (cd < cdl) {
cdl = cd;
ci = k;
}
}// End of palette loop
// add to palettacc
paletteacc[ci][0] += 128 + imgd.data[idx];
paletteacc[ci][1] += 128 + imgd.data[idx + 1];
paletteacc[ci][2] += 128 + imgd.data[idx + 2];
paletteacc[ci][3] += 128 + imgd.data[idx + 3];
paletteacc[ci][4]++;
arr[j + 1][i + 1] = ci;
}// End of i loop
}// End of Repeat clustering step "cycles" times
return new IndexedImage(arr, original_palette_backup);
}// End of colorquantization
/**
* Layering int [ ] [ ] [ ].
*
* @param ii the ii
* @return the int [ ] [ ] [ ]
*/
// 2. Layer separation and edge detection
// Edge node types ( :light or 1; :dark or 0 )
// 12
// 48
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
//
static int[][][] layering(IndexedImage ii) {
// Creating layers for each indexed color in arr
int val = 0;
int aw = ii.array[0].length;
int ah = ii.array.length;
int n1;
int n2;
int n3;
int n4;
int n5;
int n6;
int n7;
int n8;
int[][][] layers = new int[ii.palette.length][ah][aw];
// Looping through all pixels and calculating edge node type
// End of j loop
for (int j = 1; j < (ah - 1); j++)
for (int i = 1; i < (aw - 1); i++) {
// This pixel's indexed color
val = ii.array[j][i];
// Are neighbor pixel colors the same?
n1 = ii.array[j - 1][i - 1] == val ? 1 : 0;
n2 = ii.array[j - 1][i] == val ? 1 : 0;
n3 = ii.array[j - 1][i + 1] == val ? 1 : 0;
n4 = ii.array[j][i - 1] == val ? 1 : 0;
n5 = ii.array[j][i + 1] == val ? 1 : 0;
n6 = ii.array[j + 1][i - 1] == val ? 1 : 0;
n7 = ii.array[j + 1][i] == val ? 1 : 0;
n8 = ii.array[j + 1][i + 1] == val ? 1 : 0;
// this pixel"s type and looking back on previous pixels
layers[val][j + 1][i + 1] = 1 + (n5 * 2) + (n8 * 4) + (n7 * 8);
if (n4 == 0) layers[val][j + 1][i] = 0 + 2 + (n7 * 4) + (n6 * 8);
if (n2 == 0) layers[val][j][i + 1] = 0 + (n3 * 2) + (n5 * 4) + 8;
if (n1 == 0) layers[val][j][i] = 0 + (n2 * 2) + 4 + (n4 * 8);
}// End of i loop
return layers;
}// End of layering()
// 3. Walking through an edge node array, discarding edge node types 0 and 15 and creating paths from the rest.
// Walk directions (dir): 0 > ; 1 ^ ; 2 < ; 3 v
// Edge node types ( :light or 1; :dark or 0 )
//
//
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
//
private static ArrayList<ArrayList<Integer[]>> pathscan(int[][] arr, float pathomit) {
ArrayList<ArrayList<Integer[]>> paths = new ArrayList<>();
ArrayList<Integer[]> thispath;
int px = 0;
int py = 0;
int w = arr[0].length;
int h = arr.length;
int dir = 0;
boolean pathfinished = true, holepath = false;
byte[] lookuprow;
// End of i loop
// End of j loop
for (int j = 0; j < h; j++)
for (int i = 0; i < w; i++)
if ((arr[j][i] != 0) && (arr[j][i] != 15)) {
// Init
px = i;
py = j;
paths.add(new ArrayList<>());
thispath = paths.get(paths.size() - 1);
pathfinished = false;
// fill paths will be drawn, but hole paths are also required to remove unnecessary edge nodes
dir = VectorizingUtils.pathscan_dir_lookup[arr[py][px]];
holepath = VectorizingUtils.pathscan_holepath_lookup[arr[py][px]];
// Path points loop
while (!pathfinished) {
// New path point
thispath.add(new Integer[3]);
thispath.get(thispath.size() - 1)[0] = px - 1;
thispath.get(thispath.size() - 1)[1] = py - 1;
thispath.get(thispath.size() - 1)[2] = arr[py][px];
// Next: look up the replacement, direction and coordinate changes = clear this cell, turn if required, walk forward
lookuprow = VectorizingUtils.pathscan_combined_lookup[arr[py][px]][dir];
arr[py][px] = lookuprow[0];
dir = lookuprow[1];
px += lookuprow[2];
py += lookuprow[3];
// Close path
if (((px - 1) == thispath.get(0)[0]) && ((py - 1) == thispath.get(0)[1])) {
pathfinished = true;
// Discarding 'hole' type paths and paths shorter than pathomit
if ((holepath) || (thispath.size() < pathomit)) paths.remove(thispath);
}
}// End of Path points loop
}// End of Follow path
return paths;
}// End of pathscan()
/**
* Batchpathscan array list.
*
* @param layers the layers
* @param pathomit the pathomit
* @return the array list
*/
// 3. Batch pathscan
static ArrayList<ArrayList<ArrayList<Integer[]>>> batchpathscan(int[][][] layers, float pathomit) {
ArrayList<ArrayList<ArrayList<Integer[]>>> bpaths = new ArrayList<>();
for (int[][] layer : layers) bpaths.add(VectorizingUtils.pathscan(layer, pathomit));
return bpaths;
}
// 4. interpolating between path points for nodes with 8 directions ( East, SouthEast, S, SW, W, NW, N, NE )
private static ArrayList<ArrayList<Double[]>> internodes(ArrayList<ArrayList<Integer[]>> paths) {
ArrayList<ArrayList<Double[]>> ins = new ArrayList<>();
ArrayList<Double[]> thisinp;
Double[] thispoint;
Double[] nextpoint = new Double[2];
Integer[] pp1, pp2, pp3;
int palen = 0, nextidx = 0, nextidx2 = 0;
// paths loop
for (int pacnt = 0; pacnt < paths.size(); pacnt++) {
ins.add(new ArrayList<>());
thisinp = ins.get(ins.size() - 1);
palen = paths.get(pacnt).size();
// pathpoints loop
for (int pcnt = 0; pcnt < palen; pcnt++) {
// interpolate between two path points
nextidx = (pcnt + 1) % palen;
nextidx2 = (pcnt + 2) % palen;
thisinp.add(new Double[3]);
thispoint = thisinp.get(thisinp.size() - 1);
pp1 = paths.get(pacnt).get(pcnt);
pp2 = paths.get(pacnt).get(nextidx);
pp3 = paths.get(pacnt).get(nextidx2);
thispoint[0] = (pp1[0] + pp2[0]) / 2.0;
thispoint[1] = (pp1[1] + pp2[1]) / 2.0;
nextpoint[0] = (pp2[0] + pp3[0]) / 2.0;
nextpoint[1] = (pp2[1] + pp3[1]) / 2.0;
// line segment direction to the next point
// SouthEast
if (thispoint[0] < nextpoint[0]) if (thispoint[1] < nextpoint[1]) thispoint[2] = 1.0;
else // NE
// E
if (thispoint[1] > nextpoint[1]) thispoint[2] = 7.0;
else thispoint[2] = 0.0;
else // SW
// S
if (thispoint[0] > nextpoint[0]) if (thispoint[1] < nextpoint[1]) thispoint[2] = 3.0;
else // NW
// W
if (thispoint[1] > nextpoint[1]) thispoint[2] = 5.0;
else thispoint[2] = 4.0;
else if (thispoint[1] < nextpoint[1]) thispoint[2] = 2.0;
else // N
// center, this should not happen
if (thispoint[1] > nextpoint[1]) thispoint[2] = 6.0;
else thispoint[2] = 8.0;
}// End of pathpoints loop
}// End of paths loop
return ins;
}// End of internodes()
/**
* Batchinternodes array list.
*
* @param bpaths the bpaths
* @return the array list
*/
// 4. Batch interpollation
static ArrayList<ArrayList<ArrayList<Double[]>>> batchinternodes(ArrayList<ArrayList<ArrayList<Integer[]>>> bpaths) {
ArrayList<ArrayList<ArrayList<Double[]>>> binternodes = new ArrayList<>();
for (int k = 0; k < bpaths.size(); k++) binternodes.add(VectorizingUtils.internodes(bpaths.get(k)));
return binternodes;
}
// 5. tracepath() : recursively trying to fit straight and quadratic spline segments on the 8 direction internode path
// 5.1. Find sequences of points with only 2 segment types
// 5.2. Fit a straight line on the sequence
// 5.3. If the straight line fails (an error>ltreshold), find the point with the biggest error
// 5.4. Fit a quadratic spline through errorpoint (project this to get controlpoint), then measure errors on every point in the sequence
// 5.5. If the spline fails (an error>qtreshold), find the point with the biggest error, set splitpoint = (fitting point + errorpoint)/2
// 5.6. Split sequence and recursively apply 5.2. - 5.7. to startpoint-splitpoint and splitpoint-endpoint sequences
// 5.7. TODO? If splitpoint-endpoint is a spline, try to add new points from the next sequence
// This returns an SVG Path segment as a double[7] where
// segment[0] ==1.0 linear ==2.0 quadratic interpolation
// segment[1] , segment[2] : x1 , y1
// segment[3] , segment[4] : x2 , y2 ; middle point of Q curve, endpoint of L line
// segment[5] , segment[6] : x3 , y3 for Q curve, should be 0.0 , 0.0 for L line
//
// path type is discarded, no check for path.size < 3 , which should not happen
private static ArrayList<Double[]> tracepath(ArrayList<Double[]> path, float ltreshold, float qtreshold) {
int pcnt = 0, seqend = 0;
double segtype1, segtype2;
ArrayList<Double[]> smp = new ArrayList<>();
//Double [] thissegment;
int pathlength = path.size();
while (pcnt < pathlength) {
// 5.1. Find sequences of points with only 2 segment types
segtype1 = path.get(pcnt)[2];
segtype2 = -1;
seqend = pcnt + 1;
while (
((path.get(seqend)[2] == segtype1) || (path.get(seqend)[2] == segtype2) || (segtype2 == -1))
&& (seqend < (pathlength - 1))) {
if ((path.get(seqend)[2] != segtype1) && (segtype2 == -1)) segtype2 = path.get(seqend)[2];
seqend++;
}
if (seqend == (pathlength - 1)) seqend = 0;
// 5.2. - 5.6. Split sequence and recursively apply 5.2. - 5.6. to startpoint-splitpoint and splitpoint-endpoint sequences
smp.addAll(VectorizingUtils.fitseq(path, ltreshold, qtreshold, pcnt, seqend));
// 5.7. TODO? If splitpoint-endpoint is a spline, try to add new points from the next sequence
// forward pcnt;
if (seqend > 0) pcnt = seqend;
else pcnt = pathlength;
}// End of pcnt loop
return smp;
}// End of tracepath()
// 5.2. - 5.6. recursively fitting a straight or quadratic line segment on this sequence of path nodes,
// called from tracepath()
private static ArrayList<Double[]> fitseq(ArrayList<Double[]> path, float ltreshold, float qtreshold, int seqstart, int seqend) {
ArrayList<Double[]> segment = new ArrayList<>();
Double[] thissegment;
int pathlength = path.size();
// return if invalid seqend
if ((seqend > pathlength) || (seqend < 0)) return segment;
int errorpoint = seqstart;
boolean curvepass = true;
double px, py, dist2, errorval = 0;
double tl = (seqend - seqstart);
if (tl < 0) tl += pathlength;
double vx = (path.get(seqend)[0] - path.get(seqstart)[0]) / tl;
double vy = (path.get(seqend)[1] - path.get(seqstart)[1]) / tl;
// 5.2. Fit a straight line on the sequence
int pcnt = (seqstart + 1) % pathlength;
double pl;
while (pcnt != seqend) {
pl = pcnt - seqstart;
if (pl < 0) pl += pathlength;
px = path.get(seqstart)[0] + (vx * pl);
py = path.get(seqstart)[1] + (vy * pl);
dist2 = ((path.get(pcnt)[0] - px) * (path.get(pcnt)[0] - px)) + ((path.get(pcnt)[1] - py) * (path.get(pcnt)[1] - py));
if (dist2 > ltreshold) curvepass = false;
if (dist2 > errorval) {
errorpoint = pcnt;
errorval = dist2;
}
pcnt = (pcnt + 1) % pathlength;
}
// return straight line if fits
if (curvepass) {
segment.add(new Double[7]);
thissegment = segment.get(segment.size() - 1);
thissegment[0] = 1.0;
thissegment[1] = path.get(seqstart)[0];
thissegment[2] = path.get(seqstart)[1];
thissegment[3] = path.get(seqend)[0];
thissegment[4] = path.get(seqend)[1];
thissegment[5] = 0.0;
thissegment[6] = 0.0;
return segment;
}
// 5.3. If the straight line fails (an error>ltreshold), find the point with the biggest error
int fitpoint = errorpoint;
curvepass = true;
errorval = 0;
// 5.4. Fit a quadratic spline through this point, measure errors on every point in the sequence
// helpers and projecting to get control point
double t = (fitpoint - seqstart) / tl, t1 = (1.0 - t) * (1.0 - t), t2 = 2.0 * (1.0 - t) * t, t3 = t * t;
double cpx = (((t1 * path.get(seqstart)[0]) + (t3 * path.get(seqend)[0])) - path.get(fitpoint)[0]) / -t2;
double cpy = (((t1 * path.get(seqstart)[1]) + (t3 * path.get(seqend)[1])) - path.get(fitpoint)[1]) / -t2;
// Check every point
pcnt = seqstart + 1;
while (pcnt != seqend) {
t = (pcnt - seqstart) / tl;
t1 = (1.0 - t) * (1.0 - t);
t2 = 2.0 * (1.0 - t) * t;
t3 = t * t;
px = (t1 * path.get(seqstart)[0]) + (t2 * cpx) + (t3 * path.get(seqend)[0]);
py = (t1 * path.get(seqstart)[1]) + (t2 * cpy) + (t3 * path.get(seqend)[1]);
dist2 = ((path.get(pcnt)[0] - px) * (path.get(pcnt)[0] - px)) + ((path.get(pcnt)[1] - py) * (path.get(pcnt)[1] - py));
if (dist2 > qtreshold) curvepass = false;
if (dist2 > errorval) {
errorpoint = pcnt;
errorval = dist2;
}
pcnt = (pcnt + 1) % pathlength;
}
// return spline if fits
if (curvepass) {
segment.add(new Double[7]);
thissegment = segment.get(segment.size() - 1);
thissegment[0] = 2.0;
thissegment[1] = path.get(seqstart)[0];
thissegment[2] = path.get(seqstart)[1];
thissegment[3] = cpx;
thissegment[4] = cpy;
thissegment[5] = path.get(seqend)[0];
thissegment[6] = path.get(seqend)[1];
return segment;
}
// 5.5. If the spline fails (an error>qtreshold), find the point with the biggest error,
// set splitpoint = (fitting point + errorpoint)/2
int splitpoint = (fitpoint + errorpoint) / 2;
// 5.6. Split sequence and recursively apply 5.2. - 5.6. to startpoint-splitpoint and splitpoint-endpoint sequences
segment = VectorizingUtils.fitseq(path, ltreshold, qtreshold, seqstart, splitpoint);
segment.addAll(VectorizingUtils.fitseq(path, ltreshold, qtreshold, splitpoint, seqend));
return segment;
}// End of fitseq()
// 5. Batch tracing paths
private static ArrayList<ArrayList<Double[]>> batchtracepaths(ArrayList<ArrayList<Double[]>> internodepaths, float ltres, float qtres) {
ArrayList<ArrayList<Double[]>> btracedpaths = new ArrayList<>();
for (int k = 0; k < internodepaths.size(); k++)
btracedpaths.add(VectorizingUtils.tracepath(internodepaths.get(k), ltres, qtres));
return btracedpaths;
}
/**
* Batchtracelayers array list.
*
* @param binternodes the binternodes
* @param ltres the ltres
* @param qtres the qtres
* @return the array list
*/
// 5. Batch tracing layers
static ArrayList<ArrayList<ArrayList<Double[]>>> batchtracelayers(ArrayList<ArrayList<ArrayList<Double[]>>> binternodes, float ltres, float qtres) {
ArrayList<ArrayList<ArrayList<Double[]>>> btbis = new ArrayList<>();
for (int k = 0; k < binternodes.size(); k++)
btbis.add(VectorizingUtils.batchtracepaths(binternodes.get(k), ltres, qtres));
return btbis;
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright 2022 田梓萱, xcl@xuegao-tzx.top
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.xcl.imagetracer_mod;
import java.util.HashMap;
/**
* @author Xcl
* @date 2022/5/15
* @package com.xcl.imagetracer_mod
*/
public class Test {
@org.junit.Test
public void colorquantization() throws Exception {
// Options
HashMap<String, Float> options = new HashMap<>();
// Tracing
options.put("ltres", 1f);
options.put("qtres", 1f);
options.put("pathomit", 8f);
// Color quantization
options.put("colorsampling", 1f); // 1f means true ; 0f means false: starting with generated palette
options.put("numberofcolors", 16f);
options.put("mincolorratio", 0.02f);
options.put("colorquantcycles", 3f);
// SVG rendering
options.put("scale", 1f);
options.put("roundcoords", 1f); // 1f means rounded to 1 decimal places, like 7.3 ; 3f means rounded to 3 places, like 7.356 ; etc.
options.put("lcpr", 0f);
options.put("qcpr", 0f);
options.put("desc", 0f); // 1f means true ; 0f means false: SVG descriptions deactivated
options.put("viewbox", 0f); // 1f means true ; 0f means false: fixed width and height
// Selective Gauss Blur
options.put("blurradius", 0f); // 0f means deactivated; 1f .. 5f : blur with this radius
options.put("blurdelta", 20f); // smaller than this RGB difference will be blurred
// Palette
// This is an example of a grayscale palette
// please note that signed byte values [ -128 .. 127 ] will be converted to [ 0 .. 255 ] in the getsvgstring function
//下方的16建议根据具体图形自定义
byte[][] palette = new byte[16][4];
for (int colorcnt = 0; colorcnt < 16; colorcnt++) {
palette[colorcnt][0] = (byte) (-128 + colorcnt * 32); // R
palette[colorcnt][1] = (byte) (-128 + colorcnt * 32); // G
palette[colorcnt][2] = (byte) (-128 + colorcnt * 32); // B
palette[colorcnt][3] = (byte) 127; // A
}
ImageTracer.saveString(
"src\\test\\resources\\media\\panda.svg",
ImageTracer.imageToSVG("src\\test\\resources\\media\\panda.png", options, palette)
);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 96 KiB