From 3d25cad32a97b8f201044d89331a87d9372b1392 Mon Sep 17 00:00:00 2001 From: song_zhifei Date: Mon, 18 Nov 2024 09:58:22 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E9=80=82=E9=85=8Dohos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: song_zhifei --- LICENSE | 26 + OAT.xml | 91 ++ image_cropper/ohos/.gitignore | 30 + image_cropper/ohos/.metadata | 30 + image_cropper/ohos/CHANGELOG.md | 3 + image_cropper/ohos/LICENSE | 26 + image_cropper/ohos/README.md | 36 + image_cropper/ohos/analysis_options.yaml | 4 + image_cropper/ohos/example/.gitignore | 44 + image_cropper/ohos/example/README.md | 16 + .../ohos/example/analysis_options.yaml | 29 + image_cropper/ohos/example/lib/crop_page.dart | 167 ++++ image_cropper/ohos/example/lib/main.dart | 316 +++++++ image_cropper/ohos/example/ohos/.gitignore | 19 + .../ohos/example/ohos/AppScope/app.json5 | 10 + .../resources/base/element/string.json | 8 + .../resources/base/media/app_icon.png | Bin 0 -> 6790 bytes .../ohos/example/ohos/build-profile.json5 | 26 + .../ohos/example/ohos/entry/.gitignore | 7 + .../example/ohos/entry/build-profile.json5 | 29 + .../ohos/example/ohos/entry/hvigorfile.ts | 17 + .../ohos/example/ohos/entry/oh-package.json5 | 13 + .../main/ets/entryability/EntryAbility.ets | 24 + .../ohos/entry/src/main/ets/pages/Index.ets | 38 + .../ets/plugins/GeneratedPluginRegistrant.ets | 28 + .../example/ohos/entry/src/main/module.json5 | 53 ++ .../main/resources/base/element/color.json | 8 + .../main/resources/base/element/string.json | 16 + .../src/main/resources/base/media/icon.png | Bin 0 -> 6790 bytes .../resources/base/profile/main_pages.json | 5 + .../main/resources/en_US/element/string.json | 16 + .../main/resources/zh_CN/element/string.json | 16 + .../src/ohosTest/ets/test/Ability.test.ets | 50 ++ .../entry/src/ohosTest/ets/test/List.test.ets | 20 + .../ohosTest/ets/testability/TestAbility.ets | 63 ++ .../ohosTest/ets/testability/pages/Index.ets | 49 + .../ets/testrunner/OpenHarmonyTestRunner.ts | 64 ++ .../ohos/entry/src/ohosTest/module.json5 | 51 ++ .../resources/base/element/color.json | 8 + .../resources/base/element/string.json | 16 + .../ohosTest/resources/base/media/icon.png | Bin 0 -> 6790 bytes .../resources/base/profile/test_pages.json | 5 + .../example/ohos/hvigor/hvigor-config.json5 | 23 + image_cropper/ohos/example/ohos/hvigorfile.ts | 21 + .../ohos/example/ohos/oh-package.json5 | 21 + image_cropper/ohos/example/pubspec.yaml | 96 ++ .../ohos/example/test/widget_test.dart | 27 + image_cropper/ohos/lib/imagecropper_ohos.dart | 38 + .../lib/imagecropper_ohos_method_channel.dart | 55 ++ .../imagecropper_ohos_platform_interface.dart | 49 + image_cropper/ohos/lib/page/crop.dart | 849 ++++++++++++++++++ image_cropper/ohos/ohos/.gitignore | 10 + image_cropper/ohos/ohos/build-profile.json5 | 10 + image_cropper/ohos/ohos/hvigorfile.ts | 2 + image_cropper/ohos/ohos/index.ets | 17 + image_cropper/ohos/ohos/oh-package.json5 | 11 + .../plugin/ImagecropperOhosPlugin.ets | 290 ++++++ image_cropper/ohos/ohos/src/main/module.json5 | 10 + image_cropper/ohos/pubspec.yaml | 71 ++ .../ohos/test/imagecropper_ohos_test.dart | 45 + 60 files changed, 3122 insertions(+) create mode 100644 LICENSE create mode 100644 OAT.xml create mode 100644 image_cropper/ohos/.gitignore create mode 100644 image_cropper/ohos/.metadata create mode 100644 image_cropper/ohos/CHANGELOG.md create mode 100644 image_cropper/ohos/LICENSE create mode 100644 image_cropper/ohos/README.md create mode 100644 image_cropper/ohos/analysis_options.yaml create mode 100644 image_cropper/ohos/example/.gitignore create mode 100644 image_cropper/ohos/example/README.md create mode 100644 image_cropper/ohos/example/analysis_options.yaml create mode 100644 image_cropper/ohos/example/lib/crop_page.dart create mode 100644 image_cropper/ohos/example/lib/main.dart create mode 100644 image_cropper/ohos/example/ohos/.gitignore create mode 100644 image_cropper/ohos/example/ohos/AppScope/app.json5 create mode 100644 image_cropper/ohos/example/ohos/AppScope/resources/base/element/string.json create mode 100644 image_cropper/ohos/example/ohos/AppScope/resources/base/media/app_icon.png create mode 100644 image_cropper/ohos/example/ohos/build-profile.json5 create mode 100644 image_cropper/ohos/example/ohos/entry/.gitignore create mode 100644 image_cropper/ohos/example/ohos/entry/build-profile.json5 create mode 100644 image_cropper/ohos/example/ohos/entry/hvigorfile.ts create mode 100644 image_cropper/ohos/example/ohos/entry/oh-package.json5 create mode 100644 image_cropper/ohos/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets create mode 100644 image_cropper/ohos/example/ohos/entry/src/main/ets/pages/Index.ets create mode 100644 image_cropper/ohos/example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets create mode 100644 image_cropper/ohos/example/ohos/entry/src/main/module.json5 create mode 100644 image_cropper/ohos/example/ohos/entry/src/main/resources/base/element/color.json create mode 100644 image_cropper/ohos/example/ohos/entry/src/main/resources/base/element/string.json create mode 100644 image_cropper/ohos/example/ohos/entry/src/main/resources/base/media/icon.png create mode 100644 image_cropper/ohos/example/ohos/entry/src/main/resources/base/profile/main_pages.json create mode 100644 image_cropper/ohos/example/ohos/entry/src/main/resources/en_US/element/string.json create mode 100644 image_cropper/ohos/example/ohos/entry/src/main/resources/zh_CN/element/string.json create mode 100644 image_cropper/ohos/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets create mode 100644 image_cropper/ohos/example/ohos/entry/src/ohosTest/ets/test/List.test.ets create mode 100644 image_cropper/ohos/example/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets create mode 100644 image_cropper/ohos/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets create mode 100644 image_cropper/ohos/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts create mode 100644 image_cropper/ohos/example/ohos/entry/src/ohosTest/module.json5 create mode 100644 image_cropper/ohos/example/ohos/entry/src/ohosTest/resources/base/element/color.json create mode 100644 image_cropper/ohos/example/ohos/entry/src/ohosTest/resources/base/element/string.json create mode 100644 image_cropper/ohos/example/ohos/entry/src/ohosTest/resources/base/media/icon.png create mode 100644 image_cropper/ohos/example/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json create mode 100644 image_cropper/ohos/example/ohos/hvigor/hvigor-config.json5 create mode 100644 image_cropper/ohos/example/ohos/hvigorfile.ts create mode 100644 image_cropper/ohos/example/ohos/oh-package.json5 create mode 100644 image_cropper/ohos/example/pubspec.yaml create mode 100644 image_cropper/ohos/example/test/widget_test.dart create mode 100644 image_cropper/ohos/lib/imagecropper_ohos.dart create mode 100644 image_cropper/ohos/lib/imagecropper_ohos_method_channel.dart create mode 100644 image_cropper/ohos/lib/imagecropper_ohos_platform_interface.dart create mode 100644 image_cropper/ohos/lib/page/crop.dart create mode 100644 image_cropper/ohos/ohos/.gitignore create mode 100644 image_cropper/ohos/ohos/build-profile.json5 create mode 100644 image_cropper/ohos/ohos/hvigorfile.ts create mode 100644 image_cropper/ohos/ohos/index.ets create mode 100644 image_cropper/ohos/ohos/oh-package.json5 create mode 100644 image_cropper/ohos/ohos/src/main/ets/components/plugin/ImagecropperOhosPlugin.ets create mode 100644 image_cropper/ohos/ohos/src/main/module.json5 create mode 100644 image_cropper/ohos/pubspec.yaml create mode 100644 image_cropper/ohos/test/imagecropper_ohos_test.dart diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2db5f56 --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +Copyright 2013, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/OAT.xml b/OAT.xml new file mode 100644 index 0000000..bc7d794 --- /dev/null +++ b/OAT.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/image_cropper/ohos/.gitignore b/image_cropper/ohos/.gitignore new file mode 100644 index 0000000..96486fd --- /dev/null +++ b/image_cropper/ohos/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/image_cropper/ohos/.metadata b/image_cropper/ohos/.metadata new file mode 100644 index 0000000..74b8ce5 --- /dev/null +++ b/image_cropper/ohos/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: c76c05f814fa89ac912ae3355ee7fb42bd1a85ec + channel: unknown + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: c76c05f814fa89ac912ae3355ee7fb42bd1a85ec + base_revision: c76c05f814fa89ac912ae3355ee7fb42bd1a85ec + - platform: ohos + create_revision: c76c05f814fa89ac912ae3355ee7fb42bd1a85ec + base_revision: c76c05f814fa89ac912ae3355ee7fb42bd1a85ec + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/image_cropper/ohos/CHANGELOG.md b/image_cropper/ohos/CHANGELOG.md new file mode 100644 index 0000000..fd91ecf --- /dev/null +++ b/image_cropper/ohos/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +* Support OpenHarmony diff --git a/image_cropper/ohos/LICENSE b/image_cropper/ohos/LICENSE new file mode 100644 index 0000000..2db5f56 --- /dev/null +++ b/image_cropper/ohos/LICENSE @@ -0,0 +1,26 @@ +Copyright 2013, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/image_cropper/ohos/README.md b/image_cropper/ohos/README.md new file mode 100644 index 0000000..d620113 --- /dev/null +++ b/image_cropper/ohos/README.md @@ -0,0 +1,36 @@ +# Image Cropper + +[![pub package](https://img.shields.io/pub/v/image_cropper.svg)](https://pub.dartlang.org/packages/image_cropper) + + +A Flutter plugin for ohos supports cropping images. This plugin is based on three different native libraries so it comes with different UI between these platforms. + +### Required parameters + +* **sourcePath**: the absolute path of an image file. + +## Usage + +``` + yaml + dependencies: + image_cropper: 2.0.0 + imagecropper_ohos: 1.0.0 + ``` + +## Example + +````dart + +import 'package:image_cropper/image_cropper.dart'; + + + +```` + +## Options + +| Name | Android | iOS | OpenHarmony | Default | +|:---------------------|:-------:|:----:|:-----------:|:-----------:| +| cropImage | ✅ | ✅ | ✅ | `File` | + diff --git a/image_cropper/ohos/analysis_options.yaml b/image_cropper/ohos/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/image_cropper/ohos/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/image_cropper/ohos/example/.gitignore b/image_cropper/ohos/example/.gitignore new file mode 100644 index 0000000..24476c5 --- /dev/null +++ b/image_cropper/ohos/example/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/image_cropper/ohos/example/README.md b/image_cropper/ohos/example/README.md new file mode 100644 index 0000000..d9aa3ba --- /dev/null +++ b/image_cropper/ohos/example/README.md @@ -0,0 +1,16 @@ +# imagecropper_ohos_example + +Demonstrates how to use the imagecropper_ohos plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/image_cropper/ohos/example/analysis_options.yaml b/image_cropper/ohos/example/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/image_cropper/ohos/example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/image_cropper/ohos/example/lib/crop_page.dart b/image_cropper/ohos/example/lib/crop_page.dart new file mode 100644 index 0000000..78d02fc --- /dev/null +++ b/image_cropper/ohos/example/lib/crop_page.dart @@ -0,0 +1,167 @@ +import 'dart:io'; +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:imagecropper_ohos/imagecropper_ohos.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:imagecropper_ohos/page/crop.dart'; + +class CropWidget extends StatefulWidget { + final String filePath; + + CropWidget({Key? key, required this.filePath}) : super(key: key); + + @override + _MyAppState createState() => new _MyAppState(); +} + +class _MyAppState extends State { + final imageCropper = ImagecropperOhos(); + final cropKey = GlobalKey(); + File? _file; + File? _sample; + File? _lastCropped; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + imageCropper.sampleImage( + path: widget.filePath, + maximumSize: context.size!.longestSide.ceil(), + ).then((value) { + setState(() { + _sample = value!; + _file = File(widget.filePath); + }); + }); + }); + } + + + @override + void dispose() { + super.dispose(); + _sample?.delete(); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + home: SafeArea( + child: Container( + color: Colors.black, + child: _sample == null ? _buildOpeningImage() : _buildCroppingImage(), + ), + ), + ); + } + + Widget _buildOpeningImage() { + return Center(child: _buildOpenImage()); + } + + Widget _buildCroppingImage() { + return Column( + children: [ + Expanded( + child: Crop.file(_sample!, key: cropKey,), + ), + Container( + padding: const EdgeInsets.only(top: 20.0), + alignment: AlignmentDirectional.center, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + TextButton( + child: Text( + '确认', + style: Theme + .of(context) + .textTheme + .labelLarge + ?.copyWith(color: Colors.white), + ), + onPressed: () => _cropImage(), + ), + ], + ), + ) + ], + ); + } + + + Widget _buildOpenImage() { + return TextButton( + child: Text( + 'Open Image', + style: Theme + .of(context) + .textTheme + .labelLarge + ?.copyWith(color: Colors.white), + ), + onPressed: () => _openImage(), + ); + } + + Future _openImage() async { + try { + final pickedFile = await ImagePicker().pickImage( + source: ImageSource.gallery); + if (pickedFile == null) return; + final file = File(pickedFile.path); + debugPrint('$file'); + final sample = await imageCropper.sampleImage( + path: pickedFile.path, + maximumSize: context.size!.longestSide.ceil(), + ); + + _sample?.delete(); + _file?.delete(); + + setState(() { + _sample = sample; + _file = file; + }); + } catch (e, s) { + print(' _openImage $e,$s'); + } + } + + Future _cropImage() async { + final scale = cropKey.currentState?.scale; + final area = cropKey.currentState?.area; + final angle = cropKey.currentState?.angle; + final cx = cropKey.currentState?.cx ?? 0; + final cy = cropKey.currentState?.cy ?? 0; + if (area == null) { + // cannot crop, widget is not setup + return; + } + + // scale up to use maximum possible number of pixels + // this will sample image in higher resolution to make cropped image larger + final sample = await imageCropper.sampleImage( + path: _file!.path, + maximumSize: (2000 / scale!).round(), + ); + + final file = await imageCropper.cropImage( + file: sample!, + area: area, + angle: angle, + cx: cx, + cy: cy, + ); + sample.delete(); + + _lastCropped?.delete(); + _lastCropped = file; + + Navigator.pop(context, file.path); + } +} diff --git a/image_cropper/ohos/example/lib/main.dart b/image_cropper/ohos/example/lib/main.dart new file mode 100644 index 0000000..80f3ed9 --- /dev/null +++ b/image_cropper/ohos/example/lib/main.dart @@ -0,0 +1,316 @@ +import 'dart:io'; + +import 'package:dotted_border/dotted_border.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:imagecropper_ohos_example/crop_page.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + highlightColor: const Color(0xFFD0996F), + canvasColor: const Color(0xFFFDF5EC), + textTheme: TextTheme( + headlineSmall: ThemeData + .light() + .textTheme + .headlineSmall! + .copyWith(color: const Color(0xFFBC764A)), + ), + iconTheme: IconThemeData( + color: Colors.grey[600], + ), + appBarTheme: const AppBarTheme( + backgroundColor: Color(0xFFBC764A), + centerTitle: false, + foregroundColor: Colors.white, + actionsIconTheme: IconThemeData(color: Colors.white), + ), + colorScheme: ColorScheme.fromSwatch().copyWith( + background: const Color(0xFFFDF5EC), + primary: const Color(0xFFD0996F), + ), + ), + home: const HomePage(title: 'Image Cropper Demo'), + ); + } +} + +class HomePage extends StatefulWidget { + final String title; + + const HomePage({ + Key? key, + required this.title, + }) : super(key: key); + + @override + _HomePageState createState() => _HomePageState(); +} + +class _HomePageState extends State { + String? _pickedFile; + String? _croppedFile; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: !kIsWeb ? AppBar(title: Text(widget.title)) : null, + body: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (kIsWeb) + Padding( + padding: const EdgeInsets.all(kIsWeb ? 24.0 : 16.0), + child: Text( + widget.title, + style: Theme + .of(context) + .textTheme + .displayMedium! + .copyWith(color: Theme + .of(context) + .highlightColor), + ), + ), + Expanded(child: _body()), + ], + ), + ); + } + + Widget _body() { + if (_croppedFile != null || _pickedFile != null) { + return _imageCard(); + } else { + return _uploaderCard(); + } + } + + Widget _imageCard() { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: + const EdgeInsets.symmetric(horizontal: kIsWeb ? 24.0 : 16.0), + child: Card( + elevation: 4.0, + child: Padding( + padding: const EdgeInsets.all(kIsWeb ? 24.0 : 16.0), + child: _image(), + ), + ), + ), + const SizedBox(height: 24.0), + _menu(), + ], + ), + ); + } + + Widget _image() { + final screenWidth = MediaQuery + .of(context) + .size + .width; + final screenHeight = MediaQuery + .of(context) + .size + .height; + if (_croppedFile != null) { + final path = _croppedFile!; + return ConstrainedBox( + constraints: BoxConstraints( + maxWidth: 0.8 * screenWidth, + maxHeight: 0.7 * screenHeight, + ), + child: kIsWeb ? Image.network(path) : Image.file(File(path)), + ); + } else if (_pickedFile != null) { + final path = _pickedFile!; + return ConstrainedBox( + constraints: BoxConstraints( + maxWidth: 0.8 * screenWidth, + maxHeight: 0.7 * screenHeight, + ), + child: kIsWeb ? Image.network(path) : Image.file(File(path)), + ); + } else { + return const SizedBox.shrink(); + } + } + + Widget _menu() { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + FloatingActionButton( + onPressed: () { + _clear(); + }, + backgroundColor: Colors.redAccent, + tooltip: 'Delete', + child: const Icon(Icons.delete), + ), + if (_croppedFile == null) + Padding( + padding: const EdgeInsets.only(left: 32.0), + child: FloatingActionButton( + onPressed: () { + _cropImage(); + }, + backgroundColor: const Color(0xFFBC764A), + tooltip: 'Crop', + child: const Icon(Icons.crop), + ), + ) + ], + ); + } + + Widget _uploaderCard() { + return Center( + child: Card( + elevation: 4.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.0), + ), + child: SizedBox( + width: kIsWeb ? 380.0 : 320.0, + height: 300.0, + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: DottedBorder( + radius: const Radius.circular(12.0), + borderType: BorderType.RRect, + dashPattern: const [8, 4], + color: Theme + .of(context) + .highlightColor + .withOpacity(0.4), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.image, + color: Theme + .of(context) + .highlightColor, + size: 80.0, + ), + const SizedBox(height: 24.0), + Text( + 'Upload an image to start', + style: kIsWeb + ? Theme + .of(context) + .textTheme + .headlineSmall! + .copyWith( + color: Theme + .of(context) + .highlightColor) + : Theme + .of(context) + .textTheme + .bodyMedium! + .copyWith( + color: + Theme + .of(context) + .highlightColor), + ) + ], + ), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 24.0), + child: ElevatedButton( + onPressed: () { + _uploadImage(); + }, + style: + ElevatedButton.styleFrom(foregroundColor: Colors.white), + child: const Text('Upload',style: TextStyle(color: Colors.red),), + ), + ), + ], + ), + ), + ), + ); + } + + Future _cropImage() async { + if (_pickedFile != null) { + String? croppedFile = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CropWidget(filePath: _pickedFile!,)), + ); + if (croppedFile != null) { + setState(() { + _croppedFile = croppedFile; + }); + } + } + } + + Future _uploadImage() async { + final pickedFile = + await ImagePicker().pickImage(source: ImageSource.gallery); + if (pickedFile != null) { + setState(() { + _pickedFile = pickedFile.path; + }); + } + } + + + void _clear() { + setState(() { + if (_croppedFile != null) { + File(_croppedFile!).delete(); + } + _pickedFile = null; + _croppedFile = null; + }); + } + + @override + void dispose() { + if (_croppedFile != null) { + File(_croppedFile!).delete(); + } + if (_pickedFile != null) { + File(_pickedFile!).delete(); + } + super.dispose(); + } +} + diff --git a/image_cropper/ohos/example/ohos/.gitignore b/image_cropper/ohos/example/ohos/.gitignore new file mode 100644 index 0000000..6ca13b3 --- /dev/null +++ b/image_cropper/ohos/example/ohos/.gitignore @@ -0,0 +1,19 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +*.har +**/BuildProfile.ets +**/oh-package-lock.json5 + +**/src/main/resources/rawfile/flutter_assets/ +**/libs/arm64-v8a/libapp.so +**/libs/arm64-v8a/libflutter.so +**/libs/arm64-v8a/libvmservice_snapshot.so diff --git a/image_cropper/ohos/example/ohos/AppScope/app.json5 b/image_cropper/ohos/example/ohos/AppScope/app.json5 new file mode 100644 index 0000000..2ba2590 --- /dev/null +++ b/image_cropper/ohos/example/ohos/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "vn.hunghd.flutter.plugins.imagecropper.imagecropper_ohos_example", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} \ No newline at end of file diff --git a/image_cropper/ohos/example/ohos/AppScope/resources/base/element/string.json b/image_cropper/ohos/example/ohos/AppScope/resources/base/element/string.json new file mode 100644 index 0000000..f07f585 --- /dev/null +++ b/image_cropper/ohos/example/ohos/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "imagecropper_ohos_example" + } + ] +} diff --git a/image_cropper/ohos/example/ohos/AppScope/resources/base/media/app_icon.png b/image_cropper/ohos/example/ohos/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 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 { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', + JSON.stringify(data) ?? ''); + }); + } + + onWindowStageDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); + } + + onForeground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); + } + + onBackground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); + } +} \ No newline at end of file diff --git a/image_cropper/ohos/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets b/image_cropper/ohos/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets new file mode 100644 index 0000000..cef0447 --- /dev/null +++ b/image_cropper/ohos/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* 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. +*/ + +import hilog from '@ohos.hilog'; + +@Entry +@Component +struct Index { + aboutToAppear() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility index aboutToAppear'); + } + @State message: string = 'Hello World' + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + Button() { + Text('next page') + .fontSize(20) + .fontWeight(FontWeight.Bold) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .width('35%') + .height('5%') + .onClick(()=>{ + }) + } + .width('100%') + } + .height('100%') + } + } \ No newline at end of file diff --git a/image_cropper/ohos/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts b/image_cropper/ohos/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts new file mode 100644 index 0000000..1def08f --- /dev/null +++ b/image_cropper/ohos/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts @@ -0,0 +1,64 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* 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. +*/ + +import hilog from '@ohos.hilog'; +import TestRunner from '@ohos.application.testRunner'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +var abilityDelegator = undefined +var abilityDelegatorArguments = undefined + +async function onAbilityCreateCallback() { + hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback'); +} + +async function addAbilityMonitorCallback(err: any) { + hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); +} + +export default class OpenHarmonyTestRunner implements TestRunner { + constructor() { + } + + onPrepare() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare '); + } + + async onRun() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run'); + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + var testAbilityName = abilityDelegatorArguments.bundleName + '.TestAbility' + let lMonitor = { + abilityName: testAbilityName, + onAbilityCreate: onAbilityCreateCallback, + }; + abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) + var cmd = 'aa start -d 0 -a TestAbility' + ' -b ' + abilityDelegatorArguments.bundleName + var debug = abilityDelegatorArguments.parameters['-D'] + if (debug == 'true') + { + cmd += ' -D' + } + hilog.info(0x0000, 'testTag', 'cmd : %{public}s', cmd); + abilityDelegator.executeShellCommand(cmd, + (err: any, d: any) => { + hilog.info(0x0000, 'testTag', 'executeShellCommand : err : %{public}s', JSON.stringify(err) ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.stdResult ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.exitCode ?? ''); + }) + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end'); + } +} \ No newline at end of file diff --git a/image_cropper/ohos/example/ohos/entry/src/ohosTest/module.json5 b/image_cropper/ohos/example/ohos/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000..fab77ce --- /dev/null +++ b/image_cropper/ohos/example/ohos/entry/src/ohosTest/module.json5 @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* 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. +*/ + +{ + "module": { + "name": "entry_test", + "type": "feature", + "description": "$string:module_test_desc", + "mainElement": "TestAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:test_pages", + "abilities": [ + { + "name": "TestAbility", + "srcEntry": "./ets/testability/TestAbility.ets", + "description": "$string:TestAbility_desc", + "icon": "$media:icon", + "label": "$string:TestAbility_label", + "exported": true, + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "skills": [ + { + "actions": [ + "action.system.home" + ], + "entities": [ + "entity.system.home" + ] + } + ] + } + ] + } +} diff --git a/image_cropper/ohos/example/ohos/entry/src/ohosTest/resources/base/element/color.json b/image_cropper/ohos/example/ohos/entry/src/ohosTest/resources/base/element/color.json new file mode 100644 index 0000000..3c71296 --- /dev/null +++ b/image_cropper/ohos/example/ohos/entry/src/ohosTest/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/image_cropper/ohos/example/ohos/entry/src/ohosTest/resources/base/element/string.json b/image_cropper/ohos/example/ohos/entry/src/ohosTest/resources/base/element/string.json new file mode 100644 index 0000000..65d8fa5 --- /dev/null +++ b/image_cropper/ohos/example/ohos/entry/src/ohosTest/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_test_desc", + "value": "test ability description" + }, + { + "name": "TestAbility_desc", + "value": "the test ability" + }, + { + "name": "TestAbility_label", + "value": "test label" + } + ] +} \ No newline at end of file diff --git a/image_cropper/ohos/example/ohos/entry/src/ohosTest/resources/base/media/icon.png b/image_cropper/ohos/example/ohos/entry/src/ohosTest/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 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.19.6 <3.0.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + imagecropper_ohos: + # When depending on this package from a real application you should use: + # imagecropper_ohos: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + dotted_border: ^2.1.0 + image_picker: + git: + url: https://gitee.com/openharmony-sig/flutter_packages.git + path: packages/image_picker/image_picker + +dependency_overrides: + image_picker_ohos: + git: + url: https://gitee.com/openharmony-sig/flutter_packages.git + path: packages/image_picker/image_picker_ohos + ref: br_optimize_har_structure + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: +# assets: +# - images/a_dot_burr.jpeg +# - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/image_cropper/ohos/example/test/widget_test.dart b/image_cropper/ohos/example/test/widget_test.dart new file mode 100644 index 0000000..d575f33 --- /dev/null +++ b/image_cropper/ohos/example/test/widget_test.dart @@ -0,0 +1,27 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:imagecropper_ohos_example/main.dart'; + +void main() { + testWidgets('Verify Platform version', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => widget is Text && + widget.data!.startsWith('Running on:'), + ), + findsOneWidget, + ); + }); +} diff --git a/image_cropper/ohos/lib/imagecropper_ohos.dart b/image_cropper/ohos/lib/imagecropper_ohos.dart new file mode 100644 index 0000000..50c1895 --- /dev/null +++ b/image_cropper/ohos/lib/imagecropper_ohos.dart @@ -0,0 +1,38 @@ +import 'dart:io'; +import 'dart:ui'; + +import 'imagecropper_ohos_platform_interface.dart'; + +class ImagecropperOhos { + + Future sampleImage({ + required String path, + required int maximumSize, + }) async { + String? filePath = await ImagecropperOhosPlatform.instance.sampleImage(path: path,maximumSize: maximumSize); + return File(filePath!); + } + + Future cropImage({ + required File file, + required Rect area, + double? scale, + double? angle, + double? cx, + double? cy, + }) async { + String? path = await ImagecropperOhosPlatform.instance.cropImage( + sourcePath: file.path, + area: area, + scale: scale, + angle: angle, + cx: cx, + cy: cy, + ); + return File(path!); + } + + Future recoverImage() async { + return await ImagecropperOhosPlatform.instance.recoverImage(); + } +} diff --git a/image_cropper/ohos/lib/imagecropper_ohos_method_channel.dart b/image_cropper/ohos/lib/imagecropper_ohos_method_channel.dart new file mode 100644 index 0000000..d3ab7e4 --- /dev/null +++ b/image_cropper/ohos/lib/imagecropper_ohos_method_channel.dart @@ -0,0 +1,55 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'imagecropper_ohos_platform_interface.dart'; + +/// An implementation of [ImagecropperOhosPlatform] that uses method channels. +class MethodChannelImagecropperOhos extends ImagecropperOhosPlatform { + /// The method channel used to interact with the native platform. + @visibleForTesting + final methodChannel = const MethodChannel('imagecropper'); + + + @override + Future sampleImage({ + required String path, + required int maximumSize, + }) async { + String? sampleFile = await methodChannel.invokeMethod('sampleImage',{ + 'path':path, + 'maximumWidth':maximumSize, + 'maximumHeight':maximumSize, + }); + return sampleFile; + } + + @override + Future cropImage( + {required String sourcePath, + required Rect area, + double? scale, + double? angle, + double? cx, + double? cy, + }) async { + + final path = await methodChannel.invokeMethod('cropImage',{ + 'path': sourcePath, + 'left': area.left, + 'top': area.top, + 'right': area.right, + 'bottom': area.bottom, + 'scale': scale ?? 1.0, + 'angle': angle ?? 0, + 'cx': cx ?? 0, + 'cy':cy ?? 0, + }); + return path; + } + + @override + Future recoverImage() async { + final path = await methodChannel.invokeMethod('recoverImage'); + return path; + } +} diff --git a/image_cropper/ohos/lib/imagecropper_ohos_platform_interface.dart b/image_cropper/ohos/lib/imagecropper_ohos_platform_interface.dart new file mode 100644 index 0000000..794a985 --- /dev/null +++ b/image_cropper/ohos/lib/imagecropper_ohos_platform_interface.dart @@ -0,0 +1,49 @@ +import 'dart:ui'; + +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'imagecropper_ohos_method_channel.dart'; + +abstract class ImagecropperOhosPlatform extends PlatformInterface { + /// Constructs a ImagecropperOhosPlatform. + ImagecropperOhosPlatform() : super(token: _token); + + static final Object _token = Object(); + + static ImagecropperOhosPlatform _instance = MethodChannelImagecropperOhos(); + + /// The default instance of [ImagecropperOhosPlatform] to use. + /// + /// Defaults to [MethodChannelImagecropperOhos]. + static ImagecropperOhosPlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [ImagecropperOhosPlatform] when + /// they register themselves. + static set instance(ImagecropperOhosPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Future sampleImage({ + required String path, + required int maximumSize, + }) { + throw UnimplementedError('sampleImage() has not been implemented.'); + } + + Future cropImage({ + required String sourcePath, + required Rect area, + double? scale, + double? angle, + double? cx, + double? cy, + }) { + throw UnimplementedError('platformVersion() has not been implemented.'); + } + + Future recoverImage() { + throw UnimplementedError('platformVersion() has not been implemented.'); + } +} diff --git a/image_cropper/ohos/lib/page/crop.dart b/image_cropper/ohos/lib/page/crop.dart new file mode 100644 index 0000000..5447d4c --- /dev/null +++ b/image_cropper/ohos/lib/page/crop.dart @@ -0,0 +1,849 @@ +import 'dart:io'; +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/cupertino.dart'; +import 'dart:ui' as ui; + +const _kCropGridColumnCount = 3; +const _kCropGridRowCount = 3; +const _kCropGridColor = Color.fromRGBO(0xd0, 0xd0, 0xd0, 0.9); +const _kCropOverlayActiveOpacity = 0.3; +const _kCropOverlayInactiveOpacity = 0.7; +const _kCropHandleColor = Color.fromRGBO(0xd0, 0xd0, 0xd0, 1.0); +const _kCropHandleSize = 10.0; +const _kCropHandleHitSize = 48.0; +const _kCropMinFraction = 0.1; + +enum _CropAction { none, moving, cropping, scaling } + +enum _CropHandleSide { none, topLeft, topRight, bottomLeft, bottomRight } + +class RotateController { + Function(double angle)? rotate; + + void dispose() { + rotate = null; + } +} + +class Crop extends StatefulWidget { + final ImageProvider image; + final double? aspectRatio; + final double maximumScale; + final bool alwaysShowGrid; + final ImageErrorListener? onImageError; + final RotateController? rotateController; + + const Crop({ + Key? key, + required this.image, + this.aspectRatio, + this.maximumScale = 2.0, + this.alwaysShowGrid = true, + this.onImageError, + this.rotateController, + }) : super(key: key); + + Crop.file( + File file, { + Key? key, + double scale = 1.0, + this.aspectRatio, + this.maximumScale = 2.0, + this.alwaysShowGrid = true, + this.onImageError, + this.rotateController, + }) : image = FileImage(file, scale: scale), + super(key: key); + + Crop.asset( + String assetName, { + Key? key, + AssetBundle? bundle, + String? package, + this.aspectRatio, + this.maximumScale = 2.0, + this.alwaysShowGrid = true, + this.onImageError, + this.rotateController, + }) : image = AssetImage(assetName, bundle: bundle, package: package), + super(key: key); + + @override + State createState() => CropState(); + + static CropState? of(BuildContext context) => + context.findAncestorStateOfType(); +} + +class CropState extends State with TickerProviderStateMixin { + final _surfaceKey = GlobalKey(); + + late final AnimationController _activeController; + late final AnimationController _settleController; + + double _scale = 1.0; + double _ratio = 1.0; + double angle = 0; + double cx = 0; + double cy = 0; + Rect _view = Rect.zero; + Rect _area = Rect.zero; + Offset _lastFocalPoint = Offset.zero; + _CropAction _action = _CropAction.none; + _CropHandleSide _handle = _CropHandleSide.none; + + late double _startAngle; + late double _startScale; + late Rect _startView; + late Tween _viewTween; + late Tween _scaleTween; + + ImageStream? _imageStream; + ui.Image? _image; + ImageStreamListener? _imageListener; + + double get scale => _area.shortestSide / _scale; + + Rect? get area => _view.isEmpty + ? null + : Rect.fromLTWH( + max(_area.left * _view.width / _scale - _view.left, 0), + max(_area.top * _view.height / _scale - _view.top, 0), + _area.width * _view.width / _scale, + _area.height * _view.height / _scale, + ); + + bool get _isEnabled => _view.isEmpty == false && _image != null; + + // Saving the length for the widest area for different aspectRatio's + final Map _maxAreaWidthMap = {}; + + // Counting pointers(number of user fingers on screen) + int pointers = 0; + + @override + void initState() { + super.initState(); + + _activeController = AnimationController( + vsync: this, + value: widget.alwaysShowGrid ? 1.0 : 0.0, + )..addListener(() => setState(() {})); + _settleController = AnimationController(vsync: this) + ..addListener(_settleAnimationChanged); + } + + @override + void dispose() { + final listener = _imageListener; + if (listener != null) { + _imageStream?.removeListener(listener); + } + _activeController.dispose(); + _settleController.dispose(); + + super.dispose(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + _getImage(); + } + + @override + void didUpdateWidget(Crop oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.image != oldWidget.image) { + _getImage(); + } else if (widget.aspectRatio != oldWidget.aspectRatio) { + _area = _calculateDefaultArea( + viewWidth: _view.width, + viewHeight: _view.height, + imageWidth: _image?.width, + imageHeight: _image?.height, + ); + } + if (widget.alwaysShowGrid != oldWidget.alwaysShowGrid) { + if (widget.alwaysShowGrid) { + _activate(); + } else { + _deactivate(); + } + } + } + + void _getImage({bool force = false}) { + final oldImageStream = _imageStream; + final newImageStream = + widget.image.resolve(createLocalImageConfiguration(context)); + _imageStream = newImageStream; + if (newImageStream.key != oldImageStream?.key || force) { + final oldImageListener = _imageListener; + if (oldImageListener != null) { + oldImageStream?.removeListener(oldImageListener); + } + final newImageListener = + ImageStreamListener(_updateImage, onError: widget.onImageError); + _imageListener = newImageListener; + newImageStream.addListener(newImageListener); + } + } + + @override + Widget build(BuildContext context) => ConstrainedBox( + constraints: const BoxConstraints.expand(), + child: Listener( + onPointerDown: (event) => pointers++, + onPointerUp: (event) => pointers = 0, + child: GestureDetector( + key: _surfaceKey, + behavior: HitTestBehavior.opaque, + onScaleStart: _isEnabled ? _handleScaleStart : null, + onScaleUpdate: _isEnabled ? _handleScaleUpdate : null, + onScaleEnd: _isEnabled ? _handleScaleEnd : null, + child: CustomPaint( + painter: _CropPainter( + image: _image, + ratio: _ratio, + view: _view, + area: _area, + scale: _scale, + angle: angle, + active: _activeController.value, + callback: (x, y) { + cx = x; + cy = y; + }), + ), + ), + ), + ); + + void _activate() { + _activeController.animateTo( + 1.0, + curve: Curves.fastOutSlowIn, + duration: const Duration(milliseconds: 250), + ); + } + + void _deactivate() { + if (widget.alwaysShowGrid == false) { + _activeController.animateTo( + 0.0, + curve: Curves.fastOutSlowIn, + duration: const Duration(milliseconds: 250), + ); + } + } + + Size? get _boundaries { + final context = _surfaceKey.currentContext; + if (context == null) { + return null; + } + + final size = context.size; + if (size == null) { + return null; + } + + return size - const Offset(_kCropHandleSize, _kCropHandleSize) as Size; + } + + Offset? _getLocalPoint(Offset point) { + final context = _surfaceKey.currentContext; + if (context == null) { + return null; + } + + final box = context.findRenderObject() as RenderBox; + + return box.globalToLocal(point); + } + + void _settleAnimationChanged() { + setState(() { + _scale = _scaleTween.transform(_settleController.value); + final nextView = _viewTween.transform(_settleController.value); + if (nextView != null) { + _view = nextView; + } + }); + } + + Rect _calculateDefaultArea({ + required int? imageWidth, + required int? imageHeight, + required double viewWidth, + required double viewHeight, + double? defaultSize, + }) { + if (imageWidth == null || imageHeight == null) { + return Rect.zero; + } + + double height; + double width; + if ((widget.aspectRatio ?? 1.0) < 1) { + height = defaultSize ?? 1.0; + width = + ((widget.aspectRatio ?? 1.0) * imageHeight * viewHeight * height) / + imageWidth / + viewWidth; + if (width > 1.0) { + width = 1.0; + height = (imageWidth * viewWidth * width) / + (imageHeight * viewHeight * (widget.aspectRatio ?? 1.0)); + } + } else { + width = defaultSize ?? 1.0; + height = (imageWidth * viewWidth * width) / + (imageHeight * viewHeight * (widget.aspectRatio ?? 1.0)); + if (height > 1.0) { + height = 1.0; + width = + ((widget.aspectRatio ?? 1.0) * imageHeight * viewHeight * height) / + imageWidth / + viewWidth; + } + } + final aspectRatio = _maxAreaWidthMap[widget.aspectRatio]; + if (aspectRatio != null) { + _maxAreaWidthMap[aspectRatio] = width; + } + + return Rect.fromLTWH((1.0 - width) / 2, (1.0 - height) / 2, width, height); + } + + void _updateImage(ImageInfo imageInfo, bool synchronousCall) { + final boundaries = _boundaries; + if (boundaries == null) { + return; + } + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + final image = imageInfo.image; + + setState(() { + _image = image; + _scale = imageInfo.scale; + _ratio = max( + boundaries.width / image.width, + boundaries.height / image.height, + ); + + final viewWidth = boundaries.width / (image.width * _scale * _ratio); + final viewHeight = boundaries.height / (image.height * _scale * _ratio); + _area = _calculateDefaultArea( + viewWidth: viewWidth, + viewHeight: viewHeight, + imageWidth: image.width, + imageHeight: image.height, + defaultSize: 0.9, + ); + _view = Rect.fromLTWH( + (viewWidth - 1.0) / 2, + (viewHeight - 1.0) / 2, + viewWidth, + viewHeight, + ); + }); + }); + + WidgetsBinding.instance.ensureVisualUpdate(); + } + + _CropHandleSide _hitCropHandle(Offset? localPoint) { + final boundaries = _boundaries; + if (localPoint == null || boundaries == null) { + return _CropHandleSide.none; + } + + final viewRect = Rect.fromLTWH( + boundaries.width * _area.left, + boundaries.height * _area.top, + boundaries.width * _area.width, + boundaries.height * _area.height, + ).deflate(_kCropHandleSize / 2); + + if (Rect.fromLTWH( + viewRect.left - _kCropHandleHitSize / 2, + viewRect.top - _kCropHandleHitSize / 2, + _kCropHandleHitSize, + _kCropHandleHitSize, + ).contains(localPoint)) { + return _CropHandleSide.topLeft; + } + + if (Rect.fromLTWH( + viewRect.right - _kCropHandleHitSize / 2, + viewRect.top - _kCropHandleHitSize / 2, + _kCropHandleHitSize, + _kCropHandleHitSize, + ).contains(localPoint)) { + return _CropHandleSide.topRight; + } + + if (Rect.fromLTWH( + viewRect.left - _kCropHandleHitSize / 2, + viewRect.bottom - _kCropHandleHitSize / 2, + _kCropHandleHitSize, + _kCropHandleHitSize, + ).contains(localPoint)) { + return _CropHandleSide.bottomLeft; + } + + if (Rect.fromLTWH( + viewRect.right - _kCropHandleHitSize / 2, + viewRect.bottom - _kCropHandleHitSize / 2, + _kCropHandleHitSize, + _kCropHandleHitSize, + ).contains(localPoint)) { + return _CropHandleSide.bottomRight; + } + + return _CropHandleSide.none; + } + + void _handleScaleStart(ScaleStartDetails details) { + _activate(); + _settleController.stop(canceled: false); + _lastFocalPoint = details.focalPoint; + _action = _CropAction.none; + _handle = _hitCropHandle(_getLocalPoint(details.focalPoint)); + _startScale = _scale; + _startView = _view; + _startAngle = angle; + } + + Rect _getViewInBoundaries(double scale) => + Offset( + max( + min( + _view.left, + _area.left * _view.width / scale, + ), + _area.right * _view.width / scale - 1.0, + ), + max( + min( + _view.top, + _area.top * _view.height / scale, + ), + _area.bottom * _view.height / scale - 1.0, + ), + ) & + _view.size; + + double get _maximumScale => widget.maximumScale; + + double? get _minimumScale { + final boundaries = _boundaries; + final image = _image; + if (boundaries == null || image == null) { + return null; + } + + final scaleX = boundaries.width * _area.width / (image.width * _ratio); + final scaleY = boundaries.height * _area.height / (image.height * _ratio); + return min(_maximumScale, max(scaleX, scaleY)); + } + + void _handleScaleEnd(ScaleEndDetails details) { + _deactivate(); + final minimumScale = _minimumScale; + if (minimumScale == null) { + return; + } + + final targetScale = _scale.clamp(minimumScale, _maximumScale); + _scaleTween = Tween( + begin: _scale, + end: targetScale, + ); + + _startView = _view; + _viewTween = RectTween( + begin: _view, + end: _getViewInBoundaries(targetScale), + ); + + _settleController.value = 0.0; + _settleController.animateTo( + 1.0, + curve: Curves.fastOutSlowIn, + duration: const Duration(milliseconds: 350), + ); + } + + void _updateArea({ + required _CropHandleSide cropHandleSide, + double? left, + double? top, + double? right, + double? bottom, + }) { + final image = _image; + if (image == null) { + return; + } + + var areaLeft = _area.left + (left ?? 0.0); + var areaBottom = _area.bottom + (bottom ?? 0.0); + var areaTop = _area.top + (top ?? 0.0); + var areaRight = _area.right + (right ?? 0.0); + double width = areaRight - areaLeft; + double height = (image.width * _view.width * width) / + (image.height * _view.height * (widget.aspectRatio ?? 1.0)); + final maxAreaWidth = _maxAreaWidthMap[widget.aspectRatio]; + if ((height >= 1.0 || width >= 1.0) && maxAreaWidth != null) { + height = 1.0; + + if (cropHandleSide == _CropHandleSide.bottomLeft || + cropHandleSide == _CropHandleSide.topLeft) { + areaLeft = areaRight - maxAreaWidth; + } else { + areaRight = areaLeft + maxAreaWidth; + } + } + + // ensure minimum rectangle + if (areaRight - areaLeft < _kCropMinFraction) { + if (left != null) { + areaLeft = areaRight - _kCropMinFraction; + } else { + areaRight = areaLeft + _kCropMinFraction; + } + } + + if (areaBottom - areaTop < _kCropMinFraction) { + if (top != null) { + areaTop = areaBottom - _kCropMinFraction; + } else { + areaBottom = areaTop + _kCropMinFraction; + } + } + + // adjust to aspect ratio if needed + final aspectRatio = widget.aspectRatio; + if (aspectRatio != null && aspectRatio > 0.0) { + if (top != null) { + areaTop = areaBottom - height; + if (areaTop < 0.0) { + areaTop = 0.0; + areaBottom = height; + } + } else { + areaBottom = areaTop + height; + if (areaBottom > 1.0) { + areaTop = 1.0 - height; + areaBottom = 1.0; + } + } + } + + // ensure to remain within bounds of the view + if (areaLeft < 0.0) { + areaLeft = 0.0; + areaRight = _area.width; + } else if (areaRight > 1.0) { + areaLeft = 1.0 - _area.width; + areaRight = 1.0; + } + + if (areaTop < 0.0) { + areaTop = 0.0; + areaBottom = _area.height; + } else if (areaBottom > 1.0) { + areaTop = 1.0 - _area.height; + areaBottom = 1.0; + } + + setState(() { + _area = Rect.fromLTRB(areaLeft, areaTop, areaRight, areaBottom); + }); + } + + void _handleScaleUpdate(ScaleUpdateDetails details) { + if (_action == _CropAction.none) { + if (_handle == _CropHandleSide.none) { + _action = pointers == 2 ? _CropAction.scaling : _CropAction.moving; + } else { + _action = _CropAction.cropping; + } + } + + if (_action == _CropAction.cropping) { + final boundaries = _boundaries; + if (boundaries == null) { + return; + } + + final delta = details.focalPoint - _lastFocalPoint; + _lastFocalPoint = details.focalPoint; + + final dx = delta.dx / boundaries.width; + final dy = delta.dy / boundaries.height; + + if (_handle == _CropHandleSide.topLeft) { + _updateArea(left: dx, top: dy, cropHandleSide: _CropHandleSide.topLeft); + } else if (_handle == _CropHandleSide.topRight) { + _updateArea( + top: dy, right: dx, cropHandleSide: _CropHandleSide.topRight); + } else if (_handle == _CropHandleSide.bottomLeft) { + _updateArea( + left: dx, bottom: dy, cropHandleSide: _CropHandleSide.bottomLeft); + } else if (_handle == _CropHandleSide.bottomRight) { + _updateArea( + right: dx, bottom: dy, cropHandleSide: _CropHandleSide.bottomRight); + } + } else if (_action == _CropAction.moving) { + final image = _image; + if (image == null) { + return; + } + + final delta = details.focalPoint - _lastFocalPoint; + _lastFocalPoint = details.focalPoint; + + final an = angle / 180 * pi; + + var dx1 = delta.dy * sin(an); + var dx2 = delta.dx * cos(an); + + var dy1 = delta.dy * cos(an); + var dy2 = -delta.dx * sin(an); + + setState(() { + _view = _view.translate( + (dx1 + dx2) / (image.width * _scale * _ratio), + (dy1 + dy2) / (image.height * _scale * _ratio), + ); + }); + } else if (_action == _CropAction.scaling) { + final image = _image; + final boundaries = _boundaries; + if (image == null || boundaries == null) { + return; + } + + setState(() { + _scale = _startScale * details.scale; + angle = _startAngle + details.rotation / pi * 180; + + final dx = boundaries.width * + (1.0 - details.scale) / + (image.width * _scale * _ratio); + final dy = boundaries.height * + (1.0 - details.scale) / + (image.height * _scale * _ratio); + + _view = Rect.fromLTWH( + _startView.left + dx / 2, + _startView.top + dy / 2, + _startView.width, + _startView.height, + ); + }); + } + } +} + +class _CropPainter extends CustomPainter { + final ui.Image? image; + final Rect view; + final double ratio; + final Rect area; + final double scale; + final double active; + final double angle; + final Function(double cx, double cy) callback; + + _CropPainter({ + required this.image, + required this.view, + required this.ratio, + required this.area, + required this.scale, + required this.active, + required this.angle, + required this.callback, + }); + + @override + bool shouldRepaint(_CropPainter oldDelegate) { + return oldDelegate.image != image || + oldDelegate.view != view || + oldDelegate.ratio != ratio || + oldDelegate.area != area || + oldDelegate.active != active || + oldDelegate.scale != scale; + } + + @override + void paint(Canvas canvas, Size size) { + final rect = Rect.fromLTWH( + _kCropHandleSize / 2, + _kCropHandleSize / 2, + size.width - _kCropHandleSize, + size.height - _kCropHandleSize, + ); + final boundaries = Rect.fromLTWH( + rect.width * area.left, + rect.height * area.top, + rect.width * area.width, + rect.height * area.height, + ); + + canvas.save(); + canvas.translate(rect.left, rect.top); + + final paint = Paint()..isAntiAlias = false; + + final image = this.image; + if (image != null) { + final src = Rect.fromLTWH( + 0.0, + 0.0, + image.width.toDouble(), + image.height.toDouble(), + ); + final dst = Rect.fromLTWH( + view.left * image.width * scale * ratio, + view.top * image.height * scale * ratio, + image.width * scale * ratio, + image.height * scale * ratio, + ); + + //--任意中心 + var cx = (boundaries.right + boundaries.left) * 0.5; //旋转中心=画布中心 + var cy = (boundaries.bottom + boundaries.top) * 0.5; //旋转中心=画布中心 + + var cyr = cy - dst.top; + var cxr = cx - dst.left; + + callback(cxr, cyr); + + canvas.save(); + canvas.translate(cx, cy); + canvas.rotate(angle * pi / 180); + canvas.translate(-cx, -cy); + canvas.drawImageRect(image, src, dst, paint); + canvas.restore(); + } + + paint.color = Color.fromRGBO( + 0x0, + 0x0, + 0x0, + _kCropOverlayActiveOpacity * active + + _kCropOverlayInactiveOpacity * (1.0 - active)); + + canvas.drawRect(Rect.fromLTRB(0.0, 0.0, rect.width, boundaries.top), paint); + canvas.drawRect( + Rect.fromLTRB(0.0, boundaries.bottom, rect.width, rect.height), paint); + canvas.drawRect( + Rect.fromLTRB(0.0, boundaries.top, boundaries.left, boundaries.bottom), + paint); + canvas.drawRect( + Rect.fromLTRB( + boundaries.right, boundaries.top, rect.width, boundaries.bottom), + paint); + + if (boundaries.isEmpty == false) { + _drawGrid(canvas, boundaries); + _drawHandles(canvas, boundaries); + } + + canvas.restore(); + } + + void _drawHandles(Canvas canvas, Rect boundaries) { + final paint = Paint() + ..isAntiAlias = true + ..color = _kCropHandleColor; + + canvas.drawOval( + Rect.fromLTWH( + boundaries.left - _kCropHandleSize / 2, + boundaries.top - _kCropHandleSize / 2, + _kCropHandleSize, + _kCropHandleSize, + ), + paint, + ); + + canvas.drawOval( + Rect.fromLTWH( + boundaries.right - _kCropHandleSize / 2, + boundaries.top - _kCropHandleSize / 2, + _kCropHandleSize, + _kCropHandleSize, + ), + paint, + ); + + canvas.drawOval( + Rect.fromLTWH( + boundaries.right - _kCropHandleSize / 2, + boundaries.bottom - _kCropHandleSize / 2, + _kCropHandleSize, + _kCropHandleSize, + ), + paint, + ); + + canvas.drawOval( + Rect.fromLTWH( + boundaries.left - _kCropHandleSize / 2, + boundaries.bottom - _kCropHandleSize / 2, + _kCropHandleSize, + _kCropHandleSize, + ), + paint, + ); + } + + void _drawGrid(Canvas canvas, Rect boundaries) { + if (active == 0.0) return; + + final paint = Paint() + ..isAntiAlias = false + ..color = _kCropGridColor.withOpacity(_kCropGridColor.opacity * active) + ..style = PaintingStyle.stroke + ..strokeWidth = 1.0; + + final path = Path() + ..moveTo(boundaries.left, boundaries.top) + ..lineTo(boundaries.right, boundaries.top) + ..lineTo(boundaries.right, boundaries.bottom) + ..lineTo(boundaries.left, boundaries.bottom) + ..lineTo(boundaries.left, boundaries.top); + + for (var column = 1; column < _kCropGridColumnCount; column++) { + path + ..moveTo( + boundaries.left + column * boundaries.width / _kCropGridColumnCount, + boundaries.top) + ..lineTo( + boundaries.left + column * boundaries.width / _kCropGridColumnCount, + boundaries.bottom); + } + + for (var row = 1; row < _kCropGridRowCount; row++) { + path + ..moveTo(boundaries.left, + boundaries.top + row * boundaries.height / _kCropGridRowCount) + ..lineTo(boundaries.right, + boundaries.top + row * boundaries.height / _kCropGridRowCount); + } + + canvas.drawPath(path, paint); + } +} diff --git a/image_cropper/ohos/ohos/.gitignore b/image_cropper/ohos/ohos/.gitignore new file mode 100644 index 0000000..b6d2839 --- /dev/null +++ b/image_cropper/ohos/ohos/.gitignore @@ -0,0 +1,10 @@ +/node_modules +/oh_modules +/.preview +/.idea +/build +/.cxx +/.test +/BuildProfile.ets +/oh-package-lock.json5 +/local.properties diff --git a/image_cropper/ohos/ohos/build-profile.json5 b/image_cropper/ohos/ohos/build-profile.json5 new file mode 100644 index 0000000..79961f9 --- /dev/null +++ b/image_cropper/ohos/ohos/build-profile.json5 @@ -0,0 +1,10 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "targets": [ + { + "name": "default" + } + ] +} diff --git a/image_cropper/ohos/ohos/hvigorfile.ts b/image_cropper/ohos/ohos/hvigorfile.ts new file mode 100644 index 0000000..47e6e1f --- /dev/null +++ b/image_cropper/ohos/ohos/hvigorfile.ts @@ -0,0 +1,2 @@ +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +export { harTasks } from '@ohos/hvigor-ohos-plugin'; \ No newline at end of file diff --git a/image_cropper/ohos/ohos/index.ets b/image_cropper/ohos/ohos/index.ets new file mode 100644 index 0000000..29cdcac --- /dev/null +++ b/image_cropper/ohos/ohos/index.ets @@ -0,0 +1,17 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* 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. +*/ + +import ImagecropperOhosPlugin from './src/main/ets/components/plugin/ImagecropperOhosPlugin'; +export default ImagecropperOhosPlugin; diff --git a/image_cropper/ohos/ohos/oh-package.json5 b/image_cropper/ohos/ohos/oh-package.json5 new file mode 100644 index 0000000..e3d9e32 --- /dev/null +++ b/image_cropper/ohos/ohos/oh-package.json5 @@ -0,0 +1,11 @@ +{ + "name": "imagecropper_ohos", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@ohos/flutter_ohos": "file:./har/flutter.har" + } +} diff --git a/image_cropper/ohos/ohos/src/main/ets/components/plugin/ImagecropperOhosPlugin.ets b/image_cropper/ohos/ohos/src/main/ets/components/plugin/ImagecropperOhosPlugin.ets new file mode 100644 index 0000000..89903cf --- /dev/null +++ b/image_cropper/ohos/ohos/src/main/ets/components/plugin/ImagecropperOhosPlugin.ets @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2024 Huawei Device Co., Ltd. + * 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. + */ + +import { + FlutterPlugin, + FlutterPluginBinding +} from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/FlutterPlugin'; +import MethodChannel, { + MethodCallHandler, + MethodResult +} from '@ohos/flutter_ohos/src/main/ets/plugin/common/MethodChannel'; +import MethodCall from '@ohos/flutter_ohos/src/main/ets/plugin/common/MethodCall'; +import { AbilityAware, AbilityPluginBinding } from '@ohos/flutter_ohos'; +import image from '@ohos.multimedia.image'; +import { BusinessError } from '@ohos.base'; +import fs from '@ohos.file.fs'; +import { HashMap } from '@kit.ArkTS'; + +import { photoAccessHelper } from '@kit.MediaLibraryKit'; + +class ImageOptions { + private width: number = 0; + private height: number = 0; + private degrees: number = 0; + + constructor(width: number, height: number, degrees: number) { + this.width = width; + this.height = height; + this.degrees = degrees; + } + + getHeight(): number { + return (this.isFlippedDimensions() && this.degrees != 180) ? this.width : this.height; + } + + getWidth(): number { + return (this.isFlippedDimensions() && this.degrees != 180) ? this.height : this.width; + } + + getDegrees(): number { + return this.degrees; + } + + isFlippedDimensions(): boolean { + return this.degrees == 90 || this.degrees == 270 || this.degrees == 180; + } + + isRotated(): boolean { + return this.degrees != 0; + } +} + +export default class ImagecropPlugin implements FlutterPlugin, MethodCallHandler, AbilityAware { + private channel?: MethodChannel = undefined; + private abilityBinding?: AbilityPluginBinding = undefined; + + constructor() { + } + + onAttachedToAbility(binding: AbilityPluginBinding): void { + this.abilityBinding = binding; + } + + onDetachedFromAbility(): void { + + } + + getUniqueClassName(): string { + return "ImagecropPlugin1" + } + + onAttachedToEngine(binding: FlutterPluginBinding): void { + this.channel = new MethodChannel(binding.getBinaryMessenger(), "imagecropper"); + this.channel.setMethodCallHandler(this) + } + + onDetachedFromEngine(binding: FlutterPluginBinding): void { + if (this.channel != null) { + this.channel.setMethodCallHandler(null) + } + } + + onMethodCall(call: MethodCall, result: MethodResult): void { + if ("cropImage" == call.method) { + let path: string = call.argument("path"); + let scale: number = call.argument("scale"); + let left: number = call.argument("left"); + let top: number = call.argument("top"); + let right: number = call.argument("right"); + let bottom: number = call.argument("bottom"); + let angle: number = call.argument("angle"); + let cx: number = call.argument("cx"); + let cy: number = call.argument("cy"); + try { + this.cropImage(result, path, scale, left, top, right, bottom,angle,cx,cy); + } catch (e) { + result.error("INVALID", "Image source cannot be opened", null); + } + } else if ("sampleImage" == call.method) { + let path: string = call.argument("path"); + let maximumWidth: number = call.argument("maximumWidth"); + let maximumHeight: number = call.argument("maximumHeight"); + try { + this.sampleImage(result, path, maximumWidth, maximumHeight); + } catch (e) { + result.error("INVALID", "SampleImage error", null); + } + } else if ("getImageOptions" == call.method) { + let path: string = call.argument("path"); + this.getImageOptions(path, result); + } else if ("requestPermissions" == call.method) { + this.requestPermissions(result); + } else { + result.notImplemented(); + } + } + + private async cropImage(result: MethodResult, path: string, scale: number, left: number, top: number, right: number, bottom: number,angle: number,cx: number,cy:number) { + try { + let imageSource = image.createImageSource(path); + let pixelMapData = await imageSource.createPixelMap(); + let options = await this.decodeImageOptions(imageSource); + await pixelMapData.rotate(angle); + + let width = options.getWidth() * (right - left) * scale; + let height = options.getHeight() * (bottom - top) * scale; + let rAngle = 2 * Math.PI - angle/180*Math.PI; + let originX = options.getWidth() * left; + let originY = -options.getHeight() * top; + + //第三象限旋转中心坐标 + let x0 = (options.getWidth() * left + width/2); + let y0 = - (options.getHeight() * top + height/2); + + //获取图片4个角旋转后的坐标 + let leftTopX = 0; + let leftTopY = 0; + let leftTopX2 = (leftTopX - x0) * Math.cos(rAngle) - (leftTopY - y0) * Math.sin(rAngle) + x0; + let leftTopY2 = (leftTopX - x0) * Math.sin(rAngle) + (leftTopY - y0) * Math.cos(rAngle) + y0; + + let leftBottomX = 0; + let leftBottomY = -options.getHeight(); + let leftBottomX2 = (leftBottomX - x0) * Math.cos(rAngle) - (leftBottomY - y0) * Math.sin(rAngle) + x0; + let leftBottomY2 = (leftBottomX - x0) * Math.sin(rAngle) + (leftBottomY - y0) * Math.cos(rAngle) + y0; + + let rightTopX = options.getWidth(); + let rightTopY = 0; + let rightTopX2 = (rightTopX - x0) * Math.cos(rAngle) - (rightTopY - y0) * Math.sin(rAngle) + x0; + let rightTopY2 = (rightTopX - x0) * Math.sin(rAngle) + (rightTopY - y0) * Math.cos(rAngle) + y0; + + let rightBottomX = options.getWidth(); + let rightBottomY = -options.getHeight(); + let rightBottomX2 = (rightBottomX - x0) * Math.cos(rAngle) - (rightBottomY - y0) * Math.sin(rAngle) + x0; + let rightBottomY2 = (rightBottomX - x0) * Math.sin(rAngle) + (rightBottomY - y0) * Math.cos(rAngle) + y0; + + //比较旋转后的4个角的坐标,取出x轴上的最小值和y轴上的最大值,用来做初始点的偏移量 + let verifyX = Math.min(leftTopX2,leftBottomX2,rightTopX2,rightBottomX2); + let verifyY = Math.max(leftTopY2,leftBottomY2,rightTopY2,rightBottomY2); + + let region: image.Region = { + x: originX - verifyX, y: Math.abs(originY - verifyY), size: { + height: height, width: width + } + }; + + await pixelMapData.crop(region) + let dstFile = this.createTemporaryImageFilePath(); + await this.savaPixelMap(pixelMapData, dstFile); + result.success(dstFile); + } catch (e) { + } + } + + private async sampleImage(result: MethodResult, path: string, maximumWidth: number, maximumHeight: number) { + let imageSource = image.createImageSource(path); + let options = await this.decodeImageOptions(imageSource); + // 缩略图采样大小,当前只能取1。 + let inSampleSize = 1;//this.calculateInSampleSize(options.getWidth(), options.getHeight(), maximumWidth, maximumHeight); + let pixelMapData = await imageSource.createPixelMap({sampleSize: inSampleSize}); + if (options.getWidth() > maximumWidth && options.getHeight() > maximumHeight) { + let ratio = Math.max(maximumWidth / options.getWidth(), maximumHeight / options.getHeight()); + await pixelMapData.scale(ratio, ratio); + } + let dstFile = this.createTemporaryImageFilePath(); + await this.savaPixelMap(pixelMapData, dstFile); + await this.copyExif(path, dstFile); + result.success(dstFile); + } + + private async getImageOptions(path: string, result: MethodResult) { + let options = await this.decodeImageOptions(image.createImageSource(path)); + let properties = new HashMap(); + properties.set("width", options.getWidth()); + properties.set("height", options.getHeight()); + result.success(properties); + } + + private requestPermissions(result: MethodResult) { + result.success(true); + } + + private decodeImageOptions(imageSource: image.ImageSource): Promise { + return new Promise((resolve, reject) => { + let imageInfo: image.ImageInfo; + imageSource.getImageInfo().then((info: image.ImageInfo) => { + imageInfo = info + return imageSource.getImageProperty(image.PropertyKey.ORIENTATION) + }).then((result: string) => { + let value = Number.parseInt(result) + resolve(new ImageOptions(imageInfo.size.width, imageInfo.size.height, value ? value : 0)); + }).catch((err: BusinessError) => { + resolve(new ImageOptions(imageInfo.size.width, imageInfo.size.height, 0)); + }) + }) + } + + private createTemporaryImageFilePath(): string { + let directory = this.abilityBinding?.getAbility().context.cacheDir + let name = "/image_crop_" + new Date().getTime().toString() + ".jpg"; + return directory + name + } + + private async savaPixelMap(pixelMap: image.PixelMap, path: string) { + let options: image.PackingOption = { + format: 'image/jpeg', + quality: 100 + }; + let packer = image.createImagePacker(); + let imageBuffer = await packer.packing(pixelMap, options); + let stream = fs.createStreamSync(path, "a+"); + stream.writeSync(imageBuffer); + stream.closeSync(); + await pixelMap.release() + await packer.release() + } + + private async copyExif(source: string, destination: string) { + let tags = Array(); + tags.push(image.PropertyKey.F_NUMBER); + tags.push(image.PropertyKey.EXPOSURE_TIME); + tags.push(image.PropertyKey.FOCAL_LENGTH); + tags.push(image.PropertyKey.GPS_DATE_STAMP); + tags.push(image.PropertyKey.WHITE_BALANCE); + tags.push(image.PropertyKey.GPS_TIME_STAMP); + tags.push(image.PropertyKey.DATE_TIME); + tags.push(image.PropertyKey.FLASH); + tags.push(image.PropertyKey.GPS_LATITUDE); + tags.push(image.PropertyKey.GPS_LATITUDE_REF); + tags.push(image.PropertyKey.GPS_LONGITUDE); + tags.push(image.PropertyKey.GPS_LONGITUDE_REF); + tags.push(image.PropertyKey.MAKE); + tags.push(image.PropertyKey.MODEL); + tags.push(image.PropertyKey.ORIENTATION); + + let sourceExif = image.createImageSource(source); + let destinationExif = image.createImageSource(destination); + + sourceExif.getImageProperty(tags[0]).then(async (data:string)=>{ + if (data != null) { + await destinationExif.modifyImageProperty(tags[0], data); + } + }).catch((error: BusinessError)=>{ + console.info(`error: ${error}`); + }) + } + + private calculateInSampleSize(width: number, height: number, maximumWidth: number, maximumHeight: number): number { + let inSampleSize = 1; + if (height > maximumHeight || width > maximumWidth) { + let halfHeight = height / 2; + let halfWidth = width / 2; + while ((halfHeight / inSampleSize) >= maximumHeight && (halfWidth / inSampleSize) >= maximumWidth) { + inSampleSize *= 2; + } + } + return inSampleSize; + } +} diff --git a/image_cropper/ohos/ohos/src/main/module.json5 b/image_cropper/ohos/ohos/src/main/module.json5 new file mode 100644 index 0000000..5c235b6 --- /dev/null +++ b/image_cropper/ohos/ohos/src/main/module.json5 @@ -0,0 +1,10 @@ +{ + "module": { + "name": "imagecropper_ohos", + "type": "har", + "deviceTypes": [ + "default", + "tablet" + ] + } +} diff --git a/image_cropper/ohos/pubspec.yaml b/image_cropper/ohos/pubspec.yaml new file mode 100644 index 0000000..661db14 --- /dev/null +++ b/image_cropper/ohos/pubspec.yaml @@ -0,0 +1,71 @@ +name: imagecropper_ohos +description: A Flutter plugin for OHOS supports cropping images +version: 1.0.0 +homepage: https://gitee.com/openharmony-sig/fluttertpc_image_cropper + +environment: + sdk: '>=2.19.6 <4.0.0' + flutter: ">=2.5.0" + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + ohos: + package: vn.hunghd.flutter.plugins.imagecropper + pluginClass: ImagecropperOhosPlugin + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/image_cropper/ohos/test/imagecropper_ohos_test.dart b/image_cropper/ohos/test/imagecropper_ohos_test.dart new file mode 100644 index 0000000..a00aabb --- /dev/null +++ b/image_cropper/ohos/test/imagecropper_ohos_test.dart @@ -0,0 +1,45 @@ +import 'dart:io'; +import 'dart:ui'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:imagecropper_ohos/imagecropper_ohos.dart'; +import 'package:imagecropper_ohos/imagecropper_ohos_platform_interface.dart'; +import 'package:imagecropper_ohos/imagecropper_ohos_method_channel.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +class MockImagecropperOhosPlatform + with MockPlatformInterfaceMixin + implements ImagecropperOhosPlatform { + + + @override + Future cropImage({required String sourcePath, required Rect area, double? scale, double? angle, double? cx, double? cy}) async { + return "cropImage"; + } + + @override + Future recoverImage() async{ + return "recoverImage"; + } + + @override + Future sampleImage({required String path, required int maximumSize}) async{ + return "sampleImage"; + } +} + +void main() { + final ImagecropperOhosPlatform initialPlatform = ImagecropperOhosPlatform.instance; + + test('$MethodChannelImagecropperOhos is the default instance', () { + expect(initialPlatform, isInstanceOf()); + }); + + test('cropImage', () async { + ImagecropperOhos imagecropperOhosPlugin = ImagecropperOhos(); + MockImagecropperOhosPlatform fakePlatform = MockImagecropperOhosPlatform(); + ImagecropperOhosPlatform.instance = fakePlatform; + + expect((await imagecropperOhosPlugin.cropImage(file: File("filePath"), area: Rect.zero)).path, 'cropImage'); + }); +} -- Gitee From e5de6fa80146e71cd9a16d4a7cea40614b6ce295 Mon Sep 17 00:00:00 2001 From: song_zhifei Date: Mon, 18 Nov 2024 10:56:53 +0800 Subject: [PATCH 2/2] add copyright Signed-off-by: song_zhifei --- .../ets/plugins/GeneratedPluginRegistrant.ets | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/image_cropper/ohos/example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets b/image_cropper/ohos/example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets index 43bde80..ee71d56 100644 --- a/image_cropper/ohos/example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets +++ b/image_cropper/ohos/example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2024 Huawei Device Co., Ltd. + * 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. + */ + import { FlutterEngine, Log } from '@ohos/flutter_ohos'; import ImagePickerPlugin from 'image_picker_ohos'; import ImagecropperOhosPlugin from 'imagecropper_ohos'; -- Gitee