diff --git a/CHANGELOG.md b/CHANGELOG.md index a534d48..28b7100 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## [0.1.1] - 2024-12-30 + +- Fixed issues with imagefit and forceInsideCropArea resulting in crops outside the crop area and/or wrong crops + +## [0.1.0] - 2024-12-30 + +- Added maskShape so you can crop using a different mask than for visualisation (e.g. circle mask for visualisation, but square mask for cropping) +- Added imageFilter and imageFilterBlendMode so you can add filters like blur the image outside the crop area (for example ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0)) +- Fixed issue with aspectratio < 1 resulting in a to big cropSizeHeight +- [Possibly breaking] Added outlineStrokeWidth and outlineColor to CustomImageCrop to customize the outline of the crop shape, if you provided custom drawPath method, you will need to add these to the method, but you do not need to use them if you don't want to customize the outline from the CustomImageCrop widget + ## [0.0.13] - 2023-10-26 - Added forceInsideCropArea, whether image area must cover clip path. Default is false diff --git a/example/.metadata b/example/.metadata index 2438210..958d4cc 100644 --- a/example/.metadata +++ b/example/.metadata @@ -15,24 +15,9 @@ migration: - platform: root create_revision: d9111f64021372856901a1fd5bfbc386cade3318 base_revision: d9111f64021372856901a1fd5bfbc386cade3318 - - platform: android - create_revision: d9111f64021372856901a1fd5bfbc386cade3318 - base_revision: d9111f64021372856901a1fd5bfbc386cade3318 - - platform: ios - create_revision: d9111f64021372856901a1fd5bfbc386cade3318 - base_revision: d9111f64021372856901a1fd5bfbc386cade3318 - - platform: linux - create_revision: d9111f64021372856901a1fd5bfbc386cade3318 - base_revision: d9111f64021372856901a1fd5bfbc386cade3318 - platform: macos create_revision: d9111f64021372856901a1fd5bfbc386cade3318 base_revision: d9111f64021372856901a1fd5bfbc386cade3318 - - platform: web - create_revision: d9111f64021372856901a1fd5bfbc386cade3318 - base_revision: d9111f64021372856901a1fd5bfbc386cade3318 - - platform: windows - create_revision: d9111f64021372856901a1fd5bfbc386cade3318 - base_revision: d9111f64021372856901a1fd5bfbc386cade3318 # User provided section diff --git a/example/lib/main.dart b/example/lib/main.dart index 462719d..0f51c6d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,4 +1,5 @@ import 'dart:math'; +import 'dart:ui'; import 'package:custom_image_crop/custom_image_crop.dart'; import 'package:example/result_screen.dart'; @@ -101,9 +102,10 @@ class _MyHomePageState extends State { Expanded( child: CustomImageCrop( cropController: controller, - // image: const AssetImage('assets/test.png'), // Any Imageprovider will work, try with a NetworkImage for example... - image: const NetworkImage( - 'https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png'), + // forceInsideCropArea: true, + image: const AssetImage( + 'assets/test.png'), // Any Imageprovider will work, try with a AssetImage or NetworkImage for example... + // image: const NetworkImage('https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png'), shape: _currentShape, ratio: _currentShape == CustomCropShape.Ratio ? Ratio(width: _width, height: _height) @@ -114,12 +116,9 @@ class _MyHomePageState extends State { borderRadius: _currentShape == CustomCropShape.Ratio ? _radius : 0, customProgressIndicator: const CupertinoActivityIndicator(), + outlineColor: Colors.red, imageFit: _imageFit, - pathPaint: Paint() - ..color = Colors.red - ..strokeWidth = 4.0 - ..style = PaintingStyle.stroke - ..strokeJoin = StrokeJoin.round, + imageFilter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), ), ), Row( diff --git a/example/macos/.gitignore b/example/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/example/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/example/macos/Flutter/Flutter-Debug.xcconfig b/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Flutter/Flutter-Release.xcconfig b/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..cccf817 --- /dev/null +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,10 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { +} diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..c84862c --- /dev/null +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,572 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* example.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..fb7259e --- /dev/null +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/example/macos/Runner/Base.lproj/MainMenu.xib b/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/macos/Runner/Configs/AppInfo.xcconfig b/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..92fb3cd --- /dev/null +++ b/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.example + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2024 com.example. All rights reserved. diff --git a/example/macos/Runner/Configs/Debug.xcconfig b/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/example/macos/Runner/Configs/Release.xcconfig b/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/example/macos/Runner/Configs/Warnings.xcconfig b/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/example/macos/Runner/DebugProfile.entitlements b/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/example/macos/Runner/Info.plist b/example/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/example/macos/Runner/MainFlutterWindow.swift b/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..2722837 --- /dev/null +++ b/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/example/macos/Runner/Release.entitlements b/example/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/example/pubspec.lock b/example/pubspec.lock index 00fc57f..c393cb0 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -49,7 +49,7 @@ packages: path: ".." relative: true source: path - version: "0.0.12" + version: "0.1.1" fake_async: dependency: transitive description: diff --git a/lib/src/calculators/calculate_crop_fit_params.dart b/lib/src/calculators/calculate_crop_fit_params.dart index 1fc89ef..f67be86 100644 --- a/lib/src/calculators/calculate_crop_fit_params.dart +++ b/lib/src/calculators/calculate_crop_fit_params.dart @@ -12,6 +12,7 @@ CropFitParams calculateCropFitParams({ required int imageHeight, required CustomImageFit imageFit, required double aspectRatio, + required bool forceInsideCropArea, }) { /// the width of the area to crop final double cropSizeWidth; @@ -20,7 +21,7 @@ CropFitParams calculateCropFitParams({ final double cropSizeHeight; /// used to adjust image scale - final double defaultScale; + double defaultScale; switch (imageFit) { case CustomImageFit.fillCropSpace: @@ -118,6 +119,15 @@ CropFitParams calculateCropFitParams({ break; } + if (forceInsideCropArea) { + if (imageWidth * defaultScale < cropSizeWidth) { + defaultScale = cropSizeWidth / imageWidth; + } + if (imageHeight * defaultScale < cropSizeHeight) { + defaultScale = cropSizeHeight / imageHeight; + } + } + return CropFitParams( cropSizeWidth: cropSizeWidth, cropSizeHeight: cropSizeHeight, diff --git a/lib/src/calculators/calculate_on_crop_params.dart b/lib/src/calculators/calculate_on_crop_params.dart index d1cac1e..d03756b 100644 --- a/lib/src/calculators/calculate_on_crop_params.dart +++ b/lib/src/calculators/calculate_on_crop_params.dart @@ -4,7 +4,7 @@ import 'package:custom_image_crop/src/models/params_model.dart'; import 'package:custom_image_crop/src/widgets/custom_image_crop_widget.dart'; /// Returns params to use for cropping image. -OnCropParams caclulateOnCropParams({ +OnCropParams calculateOnCropParams({ required double screenWidth, required double screenHeight, required double cropPercentage, @@ -13,6 +13,7 @@ OnCropParams caclulateOnCropParams({ required int imageWidth, required int imageHeight, required CustomImageFit imageFit, + required bool forceInsideCropArea, }) { /// the size of the area to crop (width and/or height depending on the aspect ratio) final double cropSizeMax; @@ -21,7 +22,7 @@ OnCropParams caclulateOnCropParams({ final double translateScale; /// used to adjust image scale - final double scale; + double scale; /// Temp variable used to calculate the translateScale final double uiSize; @@ -55,16 +56,16 @@ OnCropParams caclulateOnCropParams({ break; case CustomImageFit.fillCropWidth: - uiSize = screenWidth; cropSizeMax = imageWidth / min(1, aspectRatio); - translateScale = cropSizeMax / (uiSize * cropPercentage); + translateScale = + cropSizeMax / (min(screenWidth, screenHeight) * cropPercentage); scale = dataScale; break; case CustomImageFit.fillCropHeight: - uiSize = screenHeight; cropSizeMax = imageHeight * max(1, aspectRatio); - translateScale = cropSizeMax / (uiSize * cropPercentage); + translateScale = + cropSizeMax / (min(screenWidth, screenHeight) * cropPercentage); scale = dataScale; break; @@ -137,6 +138,19 @@ OnCropParams caclulateOnCropParams({ cropSizeHeight = cropSizeMax / aspectRatio; cropSizeWidth = cropSizeHeight * aspectRatio; } + + if (forceInsideCropArea) { + final defaultScale = scale / dataScale; + var newDefaultScale = defaultScale; + if (imageWidth * defaultScale < cropSizeWidth) { + newDefaultScale = cropSizeWidth / imageWidth; + } + if (imageHeight * defaultScale < cropSizeHeight) { + newDefaultScale = cropSizeHeight / imageHeight; + } + scale = scale / defaultScale * newDefaultScale; + } + return OnCropParams( cropSizeHeight: cropSizeHeight, cropSizeWidth: cropSizeWidth, diff --git a/lib/src/painters/dotted_path_painter.dart b/lib/src/painters/dotted_path_painter.dart index cf72e1d..f686c44 100644 --- a/lib/src/painters/dotted_path_painter.dart +++ b/lib/src/painters/dotted_path_painter.dart @@ -10,13 +10,18 @@ class DottedCropPathPainter extends CustomPainter { /// Draw a dotted path around the given path DottedCropPathPainter( this._path, { + required this.pathPaint, this.dashWidth = 10.0, this.dashSpace = 5.0, - required this.pathPaint, }); /// Return a CustomPaint widget with the current CustomPainter - static CustomPaint drawPath(Path path, {Paint? pathPaint}) { + static CustomPaint drawPath( + Path path, { + Paint? pathPaint, + Color outlineColor = Colors.white, + double outlineStrokeWidth = 4.0, + }) { if (pathPaint != null) { return CustomPaint( painter: DottedCropPathPainter(path, pathPaint: pathPaint), @@ -26,8 +31,8 @@ class DottedCropPathPainter extends CustomPainter { painter: DottedCropPathPainter( path, pathPaint: Paint() - ..color = Colors.white - ..strokeWidth = 4.0 + ..color = outlineColor + ..strokeWidth = outlineStrokeWidth ..style = PaintingStyle.stroke ..strokeJoin = StrokeJoin.round, ), @@ -49,7 +54,10 @@ class DottedCropPathPainter extends CustomPainter { distance += dashSpace; } } - canvas.drawPath(dashPath, pathPaint); + canvas.drawPath( + dashPath, + pathPaint, + ); } @override diff --git a/lib/src/painters/solid_path_painter.dart b/lib/src/painters/solid_path_painter.dart index 6c96711..1805f88 100644 --- a/lib/src/painters/solid_path_painter.dart +++ b/lib/src/painters/solid_path_painter.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; /// Draw a solid path around the given path class SolidCropPathPainter extends CustomPainter { - static const _strokeWidth = 4.0; final Path _path; final Paint pathPaint; @@ -10,7 +9,12 @@ class SolidCropPathPainter extends CustomPainter { SolidCropPathPainter(this._path, this.pathPaint); /// Return a CustomPaint widget with the current CustomPainter - static CustomPaint drawPath(Path path, {Paint? pathPaint}) { + static CustomPaint drawPath( + Path path, { + Paint? pathPaint, + Color outlineColor = Colors.white, + double outlineStrokeWidth = 4.0, + }) { if (pathPaint != null) { return CustomPaint( painter: SolidCropPathPainter(path, pathPaint), @@ -20,8 +24,8 @@ class SolidCropPathPainter extends CustomPainter { painter: SolidCropPathPainter( path, Paint() - ..color = Colors.white - ..strokeWidth = _strokeWidth + ..color = outlineColor + ..strokeWidth = outlineStrokeWidth ..style = PaintingStyle.stroke ..strokeJoin = StrokeJoin.round, ), diff --git a/lib/src/widgets/custom_image_crop_widget.dart b/lib/src/widgets/custom_image_crop_widget.dart index 71bf1a0..fa2b45e 100644 --- a/lib/src/widgets/custom_image_crop_widget.dart +++ b/lib/src/widgets/custom_image_crop_widget.dart @@ -10,6 +10,14 @@ import 'package:flutter/material.dart'; import 'package:gesture_x_detector/gesture_x_detector.dart'; import 'package:vector_math/vector_math_64.dart' as vector_math; +/// A method that draws a path with a given paint, outline color and stroke width +typedef DrawPathMethod = CustomPaint Function( + Path path, { + Paint? pathPaint, + Color outlineColor, + double outlineStrokeWidth, +}); + /// An image cropper that is customizable. /// You can rotate, scale and translate either /// through gestures or a controller @@ -57,7 +65,7 @@ class CustomImageCrop extends StatefulWidget { /// The path drawer of the border see [DottedCropPathPainter], /// [SolidPathPainter] for more details or how to implement a /// custom one - final CustomPaint Function(Path, {Paint? pathPaint}) drawPath; + final DrawPathMethod drawPath; /// Custom paint options for drawing the cropping border. /// @@ -104,6 +112,24 @@ class CustomImageCrop extends StatefulWidget { /// If use CustomCropShape.circle, the cropped image may have white blank. final bool forceInsideCropArea; + /// Sets the color of the outline of the crop selection area + /// This is provided to the [drawPath] method + /// Default is [Colors.white] + final Color outlineColor; + + /// Sets the stroke width of the outline of the crop selection area + /// This is provided to the [drawPath] method + /// Default is 4.0 + final double outlineStrokeWidth; + + /// Adds a filter to overlay. + /// For example, consider using [ImageFilter.blur] to create a backdrop blur effect. + final ui.ImageFilter? imageFilter; + + /// The blend mode of the image filter + /// Default is [BlendMode.srcOver] + final BlendMode imageFilterBlendMode; + /// A custom image cropper widget /// /// Uses a `CustomImageCropController` to crop the image. @@ -140,8 +166,13 @@ class CustomImageCrop extends StatefulWidget { this.borderRadius = 0, Paint? imagePaintDuringCrop, this.forceInsideCropArea = false, + this.outlineColor = Colors.white, + this.outlineStrokeWidth = 4.0, + this.imageFilter, + this.imageFilterBlendMode = BlendMode.srcOver, Key? key, - }) : this.imagePaintDuringCrop = imagePaintDuringCrop ?? (Paint()..filterQuality = FilterQuality.high), + }) : this.imagePaintDuringCrop = imagePaintDuringCrop ?? + (Paint()..filterQuality = FilterQuality.high), assert( !(shape == CustomCropShape.Ratio && ratio == null), "If shape is set to Ratio, ratio should not be null.", @@ -152,7 +183,8 @@ class CustomImageCrop extends StatefulWidget { _CustomImageCropState createState() => _CustomImageCropState(); } -class _CustomImageCropState extends State with CustomImageCropListener { +class _CustomImageCropState extends State + with CustomImageCropListener { CropImageData? _dataTransitionStart; late Path _path; late Path _maskPath; @@ -226,6 +258,7 @@ class _CustomImageCropState extends State with CustomImageCropL screenHeight: _height, screenWidth: _width, aspectRatio: (widget.ratio?.width ?? 1) / (widget.ratio?.height ?? 1), + forceInsideCropArea: widget.forceInsideCropArea, ); final scale = data.scale * cropFitParams.additionalScale; _path = _getPath( @@ -248,6 +281,24 @@ class _CustomImageCropState extends State with CustomImageCropL shape: widget.maskShape!, ); + Widget overlay = Container( + color: widget.overlayColor, + ); + final filter = widget.imageFilter; + if (filter != null) { + overlay = BackdropFilter( + filter: filter, + blendMode: widget.imageFilterBlendMode, + child: overlay, + ); + } + overlay = IgnorePointer( + child: ClipPath( + clipper: InvertedClipper(_maskPath, _width, _height), + child: overlay, + ), + ); + return XGestureDetector( onMoveStart: onMoveStart, onMoveUpdate: onMoveUpdate, @@ -263,7 +314,8 @@ class _CustomImageCropState extends State with CustomImageCropL left: data.x + _width / 2, top: data.y + _height / 2, child: Transform( - transform: Matrix4.diagonal3(vector_math.Vector3(scale, scale, scale)) + transform: Matrix4.diagonal3( + vector_math.Vector3(scale, scale, scale)) ..rotateZ(data.angle) ..translate(-image.width / 2, -image.height / 2), child: Image( @@ -271,15 +323,13 @@ class _CustomImageCropState extends State with CustomImageCropL ), ), ), - IgnorePointer( - child: ClipPath( - clipper: InvertedClipper(_maskPath, _width, _height), - child: Container( - color: widget.overlayColor, - ), - ), + overlay, + widget.drawPath( + _maskPath, + pathPaint: widget.pathPaint, + outlineColor: widget.outlineColor, + outlineStrokeWidth: widget.outlineStrokeWidth, ), - widget.drawPath(_maskPath, pathPaint: widget.pathPaint), ], ), ), @@ -293,7 +343,8 @@ class _CustomImageCropState extends State with CustomImageCropL } void onScaleUpdate(ScaleEvent event) { - final scale = widget.canScale ? event.scale : (_dataTransitionStart?.scale ?? 1.0); + final scale = + widget.canScale ? event.scale : (_dataTransitionStart?.scale ?? 1.0); final angle = widget.canRotate ? event.rotationAngle : 0.0; @@ -319,7 +370,8 @@ class _CustomImageCropState extends State with CustomImageCropL void onMoveUpdate(MoveEvent event) { if (!widget.canMove) return; - widget.cropController.addTransition(CropImageData(x: event.delta.dx, y: event.delta.dy)); + widget.cropController + .addTransition(CropImageData(x: event.delta.dx, y: event.delta.dy)); } Rect _getInitialImageRect() { @@ -333,9 +385,10 @@ class _CustomImageCropState extends State with CustomImageCropL screenHeight: _height, screenWidth: _width, aspectRatio: (widget.ratio?.width ?? 1) / (widget.ratio?.height ?? 1), + forceInsideCropArea: widget.forceInsideCropArea, ); - final initialWidth = _imageAsUIImage!.width * cropFitParams.additionalScale; - final initialHeight = _imageAsUIImage!.height * cropFitParams.additionalScale; + final initialWidth = image.width * cropFitParams.additionalScale; + final initialHeight = image.height * cropFitParams.additionalScale; return Rect.fromLTWH( (_width - initialWidth) / 2, (_height - initialHeight) / 2, @@ -363,38 +416,53 @@ class _CustomImageCropState extends State with CustomImageCropL if (transition.x != 0 || transition.y != 0) { if (isRotated) { - _addTransitionInternal(CropImageData(x: startX - data.x, y: startY - data.y)); + _addTransitionInternal( + CropImageData(x: startX - data.x, y: startY - data.y)); } else { final imageRect = _getImageRect(initialImageRect, data.scale); double deltaX = min(pathRect.left - imageRect.left, 0); - deltaX = pathRect.right > imageRect.right ? pathRect.right - imageRect.right : deltaX; + deltaX = pathRect.right > imageRect.right + ? pathRect.right - imageRect.right + : deltaX; double deltaY = min(pathRect.top - imageRect.top, 0); - deltaY = pathRect.bottom > imageRect.bottom ? pathRect.bottom - imageRect.bottom : deltaY; + deltaY = pathRect.bottom > imageRect.bottom + ? pathRect.bottom - imageRect.bottom + : deltaY; _addTransitionInternal(CropImageData(x: deltaX, y: deltaY)); } return; } - double minEdgeHalf = min(initialImageRect.width, initialImageRect.height) / 2; - double adaptScale = _calculateScaleAfterRotate(pathRect, data.scale, initialImageRect, minEdgeHalf); + double minEdgeHalf = + min(initialImageRect.width, initialImageRect.height) / 2; + double adaptScale = _calculateScaleAfterRotate( + pathRect, data.scale, initialImageRect, minEdgeHalf); _addTransitionInternal(CropImageData(scale: adaptScale / data.scale)); } Rect _getImageRect(Rect initialImageRect, double currentScale) { final diffScale = (1 - currentScale) / 2; - final left = initialImageRect.left + diffScale * initialImageRect.width + data.x; - final top = initialImageRect.top + diffScale * initialImageRect.height + data.y; - Rect imageRect = Rect.fromLTWH(left, top, currentScale * initialImageRect.width, currentScale * initialImageRect.height); + final left = + initialImageRect.left + diffScale * initialImageRect.width + data.x; + final top = + initialImageRect.top + diffScale * initialImageRect.height + data.y; + Rect imageRect = Rect.fromLTWH( + left, + top, + currentScale * initialImageRect.width, + currentScale * initialImageRect.height); return imageRect; } - double _getDistanceBetweenPointAndLine(Offset point, Offset lineStart, Offset lineEnd) { + double _getDistanceBetweenPointAndLine( + Offset point, Offset lineStart, Offset lineEnd) { if (lineEnd.dy == lineStart.dy) { return (point.dy - lineStart.dy).abs(); } if (lineEnd.dx == lineStart.dx) { return (point.dx - lineStart.dx).abs(); } - double line1Slop = (lineEnd.dy - lineStart.dy) / (lineEnd.dx - lineStart.dx); + double line1Slop = + (lineEnd.dy - lineStart.dy) / (lineEnd.dx - lineStart.dx); double line1Delta = lineEnd.dy - lineEnd.dx * line1Slop; double line2Slop = -1 / line1Slop; double line2Delta = point.dy - point.dx * line2Slop; @@ -403,11 +471,13 @@ class _CustomImageCropState extends State with CustomImageCropL return (Offset(crossPointX, crossPointY) - point).distance; } - bool _isContainPath(Rect initialImageRect, Rect pathRect, double currentScale) { + bool _isContainPath( + Rect initialImageRect, Rect pathRect, double currentScale) { final imageRect = _getImageRect(initialImageRect, currentScale); Offset topLeft, topRight, bottomLeft, bottomRight; final rad = atan(imageRect.height / imageRect.width); - final len = sqrt(pow(imageRect.width / 2, 2) + pow(imageRect.height / 2, 2)); + final len = + sqrt(pow(imageRect.width / 2, 2) + pow(imageRect.height / 2, 2)); bool isRotated = data.angle != 0; if (isRotated) { @@ -418,9 +488,11 @@ class _CustomImageCropState extends State with CustomImageCropL final cosCounterClockValue = len * cos(counterClockAngle); final sinCounterClockValue = len * sin(counterClockAngle); bottomRight = imageRect.center.translate(cosClockValue, sinClockValue); - topRight = imageRect.center.translate(cosCounterClockValue, -sinCounterClockValue); + topRight = imageRect.center + .translate(cosCounterClockValue, -sinCounterClockValue); topLeft = imageRect.center.translate(-cosClockValue, -sinClockValue); - bottomLeft = imageRect.center.translate(-cosCounterClockValue, sinCounterClockValue); + bottomLeft = imageRect.center + .translate(-cosCounterClockValue, sinCounterClockValue); } else { bottomRight = imageRect.bottomRight; topRight = imageRect.topRight; @@ -431,10 +503,15 @@ class _CustomImageCropState extends State with CustomImageCropL if (widget.shape == CustomCropShape.Circle) { final anchor = max(pathRect.width, pathRect.height) / 2; final pathCenter = pathRect.center; - return _getDistanceBetweenPointAndLine(pathCenter, topLeft, topRight) >= anchor && - _getDistanceBetweenPointAndLine(pathCenter, topRight, bottomRight) >= anchor && - _getDistanceBetweenPointAndLine(pathCenter, bottomLeft, bottomRight) >= anchor && - _getDistanceBetweenPointAndLine(pathCenter, topLeft, bottomLeft) >= anchor; + return _getDistanceBetweenPointAndLine(pathCenter, topLeft, topRight) >= + anchor && + _getDistanceBetweenPointAndLine(pathCenter, topRight, bottomRight) >= + anchor && + _getDistanceBetweenPointAndLine( + pathCenter, bottomLeft, bottomRight) >= + anchor && + _getDistanceBetweenPointAndLine(pathCenter, topLeft, bottomLeft) >= + anchor; } if (isRotated) { @@ -444,19 +521,28 @@ class _CustomImageCropState extends State with CustomImageCropL ..lineTo(bottomRight.dx, bottomRight.dy) ..lineTo(bottomLeft.dx, bottomLeft.dy) ..close(); - return imagePath.contains(pathRect.topLeft) && imagePath.contains(pathRect.topRight) && imagePath.contains(pathRect.bottomLeft) && imagePath.contains(pathRect.bottomRight); + return imagePath.contains(pathRect.topLeft) && + imagePath.contains(pathRect.topRight) && + imagePath.contains(pathRect.bottomLeft) && + imagePath.contains(pathRect.bottomRight); } else { - return imageRect.contains(pathRect.topLeft) && imageRect.contains(pathRect.topRight) && imageRect.contains(pathRect.bottomLeft) && imageRect.contains(pathRect.bottomRight); + return imageRect.contains(pathRect.topLeft) && + imageRect.contains(pathRect.topRight) && + imageRect.contains(pathRect.bottomLeft) && + imageRect.contains(pathRect.bottomRight); } } - double _calculateScaleAfterRotate(Rect pathRect, double startScale, Rect initialImageRect, double minEdgeHalf) { + double _calculateScaleAfterRotate(Rect pathRect, double startScale, + Rect initialImageRect, double minEdgeHalf) { final imageCenter = initialImageRect.center.translate(data.x, data.y); final topLeftDistance = (pathRect.topLeft - imageCenter).distance; final topRightDistance = (pathRect.topRight - imageCenter).distance; final bottomLeftDistance = (pathRect.bottomLeft - imageCenter).distance; final bottomRightDistance = (pathRect.bottomRight - imageCenter).distance; - final maxDistance = max(max(max(topLeftDistance, topRightDistance), bottomLeftDistance), bottomRightDistance); + final maxDistance = max( + max(max(topLeftDistance, topRightDistance), bottomLeftDistance), + bottomRightDistance); double endScale = maxDistance / minEdgeHalf; if (startScale >= endScale) { @@ -544,7 +630,7 @@ class _CustomImageCropState extends State with CustomImageCropL final imageHeight = _imageAsUIImage!.height; final pictureRecorder = ui.PictureRecorder(); final canvas = Canvas(pictureRecorder); - final onCropParams = caclulateOnCropParams( + final onCropParams = calculateOnCropParams( cropPercentage: widget.cropPercentage, imageFit: widget.imageFit, imageHeight: imageHeight, @@ -553,6 +639,7 @@ class _CustomImageCropState extends State with CustomImageCropL screenWidth: _width, dataScale: data.scale, aspectRatio: (widget.ratio?.width ?? 1) / (widget.ratio?.height ?? 1), + forceInsideCropArea: widget.forceInsideCropArea, ); final clipPath = Path.from(_getPath( cropWidth: onCropParams.cropSizeWidth, diff --git a/pubspec.yaml b/pubspec.yaml index b6ed7ea..649078a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: custom_image_crop description: An image cropper that is customizable. You can rotate, scale and translate either through gestures or a controller -version: 0.0.13 +version: 0.1.1 homepage: https://github.com/icapps/flutter-custom-image-crop environment: @@ -16,6 +16,3 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - -flutter: -