Compare commits
1 Commits
example-ap
...
wip
| Author | SHA1 | Date | |
|---|---|---|---|
|
779f119992
|
@@ -1,5 +1,10 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math' show min, sqrt, pow;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_svg/flutter_svg.dart' as svg;
|
||||
import 'package:image/image.dart' as image;
|
||||
|
||||
import '../svg/parser.dart';
|
||||
import '../common/point.dart';
|
||||
@@ -144,31 +149,13 @@ void clearPreviousElements(XmlDocument doc) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Note: setting any color to transparent will result in a much bigger
|
||||
/// filesize for GIFs.
|
||||
void createAnimation({
|
||||
required String inputFile,
|
||||
String? outputFile,
|
||||
TimingFunction timingFunction = TimingFunction.easeInOut,
|
||||
double strokeBorderWidth = 4.5,
|
||||
double strokeUnfilledWidth = 3,
|
||||
double strokeFilledWidth = 3.1,
|
||||
bool showBrush = true,
|
||||
bool showBrushFrontBorder = true,
|
||||
double brushWidth = 5.5,
|
||||
double brushBorderWidth = 7,
|
||||
double waitAfter = 1.5,
|
||||
String strokeBorderColor = '#666',
|
||||
String strokeUnfilledColor = '#EEE',
|
||||
String strokeFillingColor = '#F00',
|
||||
String strokeFilledColor = '#000',
|
||||
String brushColor = '#F00',
|
||||
String brushBorderColor = '#666',
|
||||
}) {
|
||||
XmlDocument fetchXML(String inputFile) {
|
||||
|
||||
//------------------------------
|
||||
// FETCH DATA FILE
|
||||
//------------------------------
|
||||
|
||||
print('processing $inputFile');
|
||||
final String filenameNoext = inputFile.replaceAll(RegExp(r'\.[^\.]+$'), '');
|
||||
outputFile ??= '${filenameNoext}_anim.svg';
|
||||
final String baseid = basename(filenameNoext);
|
||||
|
||||
// load xml
|
||||
final XmlDocument doc = XmlDocument.parse(File(inputFile).readAsStringSync());
|
||||
@@ -178,6 +165,44 @@ void createAnimation({
|
||||
doc.rootElement.setAttribute('xlink:used', '');
|
||||
|
||||
clearPreviousElements(doc);
|
||||
return doc;
|
||||
}
|
||||
|
||||
// void generateCssForPath(XmlNode p, void Function<String> addCss) {
|
||||
// }
|
||||
|
||||
/// Note: setting any color to transparent will result in a much bigger
|
||||
/// filesize for GIFs.
|
||||
FutureOr<void> createAnimation({
|
||||
required String kanji,
|
||||
String? outputFile,
|
||||
TimingFunction timingFunction = TimingFunction.easeInOut,
|
||||
double strokeBorderWidth = 4.5,
|
||||
double strokeUnfilledWidth = 3,
|
||||
double strokeFilledWidth = 3.1,
|
||||
bool showBrush = true,
|
||||
// bool showNumbers = false,
|
||||
bool showBrushFrontBorder = true,
|
||||
double brushWidth = 5.5,
|
||||
double brushBorderWidth = 7,
|
||||
double waitAfter = 1.5,
|
||||
int gifSize = 500,
|
||||
String strokeBorderColor = '#666',
|
||||
String strokeUnfilledColor = '#EEE',
|
||||
String strokeFillingColor = '#F00',
|
||||
String strokeFilledColor = '#000',
|
||||
String brushColor = '#F00',
|
||||
String brushBorderColor = '#666',
|
||||
}) async {
|
||||
final inputFile = 'assets/kanjivg/kanji/${kanji.codeUnitAt(0).toRadixString(16).padLeft(5, '0')}.svg';
|
||||
final String filenameNoext = inputFile.replaceAll(RegExp(r'\.[^\.]+$'), '');
|
||||
outputFile ??= '${filenameNoext}_anim.svg';
|
||||
final String baseid = basename(filenameNoext);
|
||||
final doc = fetchXML(inputFile);
|
||||
|
||||
//------------------------------
|
||||
// CREATE SVG PATH GROUPS
|
||||
//------------------------------
|
||||
|
||||
/// create groups with a copies (references actually) of the paths
|
||||
XmlDocumentFragment pathCopyGroup({
|
||||
@@ -227,6 +252,10 @@ void createAnimation({
|
||||
);
|
||||
}
|
||||
|
||||
//------------------------------
|
||||
// CALCULATE STROKE TIMES
|
||||
//------------------------------
|
||||
|
||||
// compute total length and time, at first
|
||||
double totlen = 0;
|
||||
double tottime = 0;
|
||||
@@ -254,6 +283,10 @@ void createAnimation({
|
||||
final double actualAnimationTime = animationTime;
|
||||
animationTime += waitAfter;
|
||||
|
||||
//------------------------------
|
||||
// START ADDING CSS
|
||||
//------------------------------
|
||||
|
||||
final Map<int, String> staticCss = {};
|
||||
late String animatedCss;
|
||||
|
||||
@@ -286,6 +319,10 @@ void createAnimation({
|
||||
double elapsedlen = 0;
|
||||
double elapsedtime = 0;
|
||||
|
||||
//------------------------------
|
||||
// ADD CSS FOR STROKE STYLE
|
||||
//------------------------------
|
||||
|
||||
// add css elements for all strokes
|
||||
for (final XmlNode g in doc
|
||||
.getElement('svg', namespace: namespaces['n'])!
|
||||
@@ -326,7 +363,12 @@ void createAnimation({
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------
|
||||
// ADD CSS FOR HREFS
|
||||
//------------------------------
|
||||
|
||||
for (final p in g.findAllElements("path", namespace: namespaces['n'])) {
|
||||
|
||||
final pathid = p.getAttribute('id') as String;
|
||||
final pathidcss = pathid.replaceAll(':', '\\3a ');
|
||||
|
||||
@@ -355,6 +397,10 @@ void createAnimation({
|
||||
brushBorderGroupElement = addHref('brush-brd', brushBrdGroup);
|
||||
}
|
||||
|
||||
//------------------------------
|
||||
// CALCULATE RELATIVE TIMING
|
||||
//------------------------------
|
||||
|
||||
final pathname = pathid.replaceAll(RegExp(r'^kvg:'), '');
|
||||
final pathlen = _computePathLength(p.getAttribute('d') as String);
|
||||
final duration = strokeLengthToDuration(pathlen);
|
||||
@@ -378,6 +424,10 @@ void createAnimation({
|
||||
final animStart = elapsedtime / tottime * 100;
|
||||
final animEnd = newelapsedtime / tottime * 100;
|
||||
|
||||
//------------------------------
|
||||
// GENERATE SVG SPECIFIC ANIMATION CSS
|
||||
//------------------------------
|
||||
|
||||
if (GENERATE_SVG) {
|
||||
// animation stroke progression
|
||||
animatedCss += '''
|
||||
@@ -418,6 +468,10 @@ void createAnimation({
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------
|
||||
// GENERATE JS SVG SPECIFIC ANIMATION CSS
|
||||
//------------------------------
|
||||
|
||||
if (GENERATE_JS_SVG) {
|
||||
jsAnimatedCss += '\n/* stroke $pathid */\n';
|
||||
|
||||
@@ -468,6 +522,10 @@ void createAnimation({
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------
|
||||
// GENERATE GIF SPECIFIC STATIC FRAME CSS
|
||||
//------------------------------
|
||||
|
||||
if (GENERATE_GIF) {
|
||||
for (final k in staticCss.keys) {
|
||||
final time = k * GIF_FRAME_DURATION;
|
||||
@@ -539,17 +597,32 @@ void createAnimation({
|
||||
elapsedlen = newelapsedlen;
|
||||
elapsedtime = newelapsedtime;
|
||||
}
|
||||
|
||||
// for (final p in g.findAllElements("path", namespace: namespaces['n'])) {
|
||||
// generateCssForPath(p);
|
||||
// }
|
||||
}
|
||||
|
||||
//------------------------------
|
||||
// ADD CSS TO XML-OBJECT INSTANCE
|
||||
//------------------------------
|
||||
|
||||
void addGroup(XmlDocumentFragment g) =>
|
||||
doc.root.firstElementChild?.children.add(g);
|
||||
|
||||
// insert groups
|
||||
if (showBrush && !showBrushFrontBorder) addGroup(brushBrdGroup);
|
||||
if (showBrush && !showBrushFrontBorder)
|
||||
addGroup(brushBrdGroup);
|
||||
addGroup(bgGroup);
|
||||
if (showBrush && showBrushFrontBorder) addGroup(brushBrdGroup);
|
||||
if (showBrush && showBrushFrontBorder)
|
||||
addGroup(brushBrdGroup);
|
||||
addGroup(animGroup);
|
||||
if (showBrush) addGroup(brushGroup);
|
||||
if (showBrush)
|
||||
addGroup(brushGroup);
|
||||
|
||||
//------------------------------
|
||||
// PRODUCE SVG
|
||||
//------------------------------
|
||||
|
||||
if (GENERATE_SVG) {
|
||||
print(animatedCss);
|
||||
@@ -567,24 +640,51 @@ void createAnimation({
|
||||
print('written $outputFile');
|
||||
}
|
||||
|
||||
if (GENERATE_GIF) {
|
||||
// svgframefiles = []
|
||||
// pngframefiles = []
|
||||
// svgexport_data = []
|
||||
// for k in static_css:
|
||||
// svgframefile = filename_noext_ascii + ("_frame%04d.svg"%k)
|
||||
// pngframefile = filename_noext_ascii + ("_frame%04d.png"%k)
|
||||
// svgframefiles.append(svgframefile)
|
||||
// pngframefiles.append(pngframefile)
|
||||
// svgexport_data.append({"input": [abspath(svgframefile)],
|
||||
// "output": [[abspath(pngframefile),
|
||||
// "%d:%d"% (GIF_SIZE, GIF_SIZE)]]})
|
||||
//------------------------------
|
||||
// PRODUCE GIF
|
||||
//------------------------------
|
||||
|
||||
// style = E.style(static_css[k], id="style-Kanimaji")
|
||||
// doc.getroot().insert(0, style)
|
||||
// doc.write(svgframefile, pretty_print=True)
|
||||
// doc.getroot().remove(style)
|
||||
// print 'written %s' % svgframefile
|
||||
if (GENERATE_GIF) {
|
||||
// var svgframefiles = [];
|
||||
// var pngframefiles = [];
|
||||
// var svgexport_data = [];
|
||||
|
||||
final encoder = image.GifEncoder(dither: 0 as DitherKernel);
|
||||
|
||||
for (var k in staticCss.keys) {
|
||||
final style = XmlBuilder()..element(
|
||||
'style',
|
||||
attributes: {'id': 'style-Kanimaji'},
|
||||
);
|
||||
doc.children.insert(0, style.buildFragment()..innerXml = staticCss[k]!);
|
||||
|
||||
final svg.DrawableRoot svgRoot = await svg.svg.fromSvgString(doc.outerXml, doc.outerXml);
|
||||
final picture = await svgRoot.toPicture().toImage(gifSize, gifSize);
|
||||
final ByteData? bytes = await picture.toByteData();
|
||||
final intBytes = bytes!.buffer.asInt32List().toList();
|
||||
image.Image frame = image.Image.fromBytes(gifSize, gifSize, intBytes);
|
||||
encoder.addFrame(frame);
|
||||
|
||||
doc.children.removeAt(0);
|
||||
|
||||
// svgframefile = filename_noext_ascii + ("_frame%04d.svg"%k);
|
||||
// pngframefile = filename_noext_ascii + ("_frame%04d.png"%k);
|
||||
// svgframefiles.append(svgframefile)
|
||||
// pngframefiles.append(pngframefile)
|
||||
// svgexport_data.append({"input": [svgframefile.abs()],
|
||||
// "output": [[abspath(pngframefile),
|
||||
// "%d:%d"% (GIF_SIZE, GIF_SIZE)]]})
|
||||
|
||||
// style = E.style(staticCss[k], id="style-Kanimaji")
|
||||
// doc.getroot().insert(0, style)
|
||||
// doc.write(svgframefile, pretty_print=True)
|
||||
// doc.getroot().remove(style)
|
||||
// print 'written %s' % svgframefile
|
||||
}
|
||||
|
||||
// encoder.finish();
|
||||
|
||||
File(outputFile).writeAsBytesSync(encoder.finish()!);
|
||||
|
||||
// // create json file
|
||||
// svgexport_datafile = filename_noext_ascii+"_export_data.json"
|
||||
@@ -675,28 +775,4 @@ void createAnimation({
|
||||
// doc.getroot().remove(style)
|
||||
// print('written $svgfile');
|
||||
// }
|
||||
}
|
||||
|
||||
void main(List<String> args) {
|
||||
// createAnimation('assets/kanjivg/kanji/0f9b1.svg');
|
||||
|
||||
const kanji = '実例';
|
||||
final fileList = [];
|
||||
for (int k = 0; k < kanji.length; k++) {
|
||||
createAnimation(
|
||||
inputFile: 'assets/kanjivg/kanji/${kanji.codeUnits[k].toRadixString(16).padLeft(5, '0')}.svg',
|
||||
outputFile: '${k+1}.svg',
|
||||
);
|
||||
fileList.add('${k+1}.svg');
|
||||
}
|
||||
|
||||
File('index.html').writeAsStringSync(
|
||||
'<html>' +
|
||||
fileList.map((e) => File(e).readAsStringSync().replaceAll(']>', '')).join('\n') +
|
||||
'</html>'
|
||||
);
|
||||
// createAnimation(
|
||||
// inputFile: 'assets/kanjivg/kanji/060c5.svg',
|
||||
// outputFile: 'test.svg',
|
||||
// );
|
||||
}
|
||||
}
|
||||
0
lib/kanimaji/commandline.dart
Normal file
0
lib/kanimaji/commandline.dart
Normal file
6
lib/kanimaji/gif.dart
Normal file
6
lib/kanimaji/gif.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
import 'package:kanimaji/kanimaji/options.dart';
|
||||
|
||||
Future<void> createGif(String kanji, KanimajiOptions options) async {
|
||||
|
||||
}
|
||||
0
lib/kanimaji/js_svg.dart
Normal file
0
lib/kanimaji/js_svg.dart
Normal file
24
lib/kanimaji/options.dart
Normal file
24
lib/kanimaji/options.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
import 'dart:core';
|
||||
|
||||
import 'animate_kanji.dart';
|
||||
|
||||
class KanimajiOptions {
|
||||
// String inputFile;
|
||||
// String? outputFile;
|
||||
TimingFunction timingFunction = TimingFunction.easeInOut;
|
||||
double strokeBorderWidth = 4.5;
|
||||
double strokeUnfilledWidth = 3;
|
||||
double strokeFilledWidth = 3.1;
|
||||
bool showBrush = true;
|
||||
bool showBrushFrontBorder = true;
|
||||
double brushWidth = 5.5;
|
||||
double brushBorderWidth = 7;
|
||||
double waitAfter = 1.5;
|
||||
int gifSize = 500;
|
||||
String strokeBorderColor = '#666';
|
||||
String strokeUnfilledColor = '#EEE';
|
||||
String strokeFillingColor = '#F00';
|
||||
String strokeFilledColor = '#000';
|
||||
String brushColor = '#F00';
|
||||
String brushBorderColor = '#666';
|
||||
}
|
||||
3
lib/kanimaji/svg.dart
Normal file
3
lib/kanimaji/svg.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
// String calculate_css() {
|
||||
|
||||
// }
|
||||
@@ -3,6 +3,9 @@ description: A new Flutter package project.
|
||||
version: 0.0.1
|
||||
homepage:
|
||||
|
||||
executables:
|
||||
kanimaji: lib/kanimaji/animate_kanji
|
||||
|
||||
environment:
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
flutter: ">=1.17.0"
|
||||
@@ -11,6 +14,9 @@ dependencies:
|
||||
bisection: ^0.4.3+1
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_svg: ^1.0.3
|
||||
gifencoder: ^1.0.0
|
||||
image: ^3.1.3
|
||||
xml: ^5.3.1
|
||||
|
||||
dev_dependencies:
|
||||
|
||||
@@ -42,11 +42,14 @@ import 'package:kanimaji/svg/path.dart';
|
||||
// }
|
||||
|
||||
class MoreOrLessEqualsToPoint extends Matcher {
|
||||
const MoreOrLessEqualsToPoint();
|
||||
static const double threshold = 0.000001;
|
||||
final Point _expected;
|
||||
|
||||
const MoreOrLessEqualsToPoint(this._expected);
|
||||
|
||||
@override
|
||||
bool matches(covariant Finder finder, Map<dynamic, dynamic> matchState)
|
||||
=> finder is Point && finder.x
|
||||
bool matches(covariant Object? object, Map<dynamic, dynamic> matchState)
|
||||
=> object is Point && (_expected.x - object.x).abs() >=threshold && (_expected.y - object.y).abs() >=threshold;
|
||||
|
||||
@override
|
||||
Description describe(Description description) => description.add('in card');
|
||||
@@ -83,19 +86,19 @@ test('Test lines', () {
|
||||
expect(line3.size(), moreOrLessEquals(500));
|
||||
});
|
||||
|
||||
def test_equality(self):
|
||||
// This is to test the __eq__ and __ne__ methods, so we can't use
|
||||
// assertEqual and assertNotEqual
|
||||
line = Line(0j, 400 + 0j)
|
||||
self.assertTrue(line == Line(0, 400))
|
||||
self.assertTrue(line != Line(100, 400))
|
||||
self.assertFalse(line == str(line))
|
||||
self.assertTrue(line != str(line))
|
||||
self.assertFalse(
|
||||
CubicBezier(600 + 500j, 600 + 350j, 900 + 650j, 900 + 500j) == line
|
||||
)
|
||||
// def test_equality(self):
|
||||
// // This is to test the __eq__ and __ne__ methods, so we can't use
|
||||
// // assertEqual and assertNotEqual
|
||||
// line = Line(0j, 400 + 0j)
|
||||
// self.assertTrue(line == Line(0, 400))
|
||||
// self.assertTrue(line != Line(100, 400))
|
||||
// self.assertFalse(line == str(line))
|
||||
// self.assertTrue(line != str(line))
|
||||
// self.assertFalse(
|
||||
// CubicBezier(600 + 500j, 600 + 350j, 900 + 650j, 900 + 500j) == line
|
||||
// )
|
||||
|
||||
});
|
||||
// });
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user