diff --git a/melos.yaml b/melos.yaml index 62600368..770ebf49 100644 --- a/melos.yaml +++ b/melos.yaml @@ -11,6 +11,8 @@ scripts: exec: flutter pub get test: exec: flutter test + packageFilters: + dirExists: test format: exec: dart format --set-exit-if-changed . try_build_apk: diff --git a/packages/flutter_image_compress/example/.gitignore b/packages/flutter_image_compress/example/.gitignore index a5408d96..6b539f1a 100644 --- a/packages/flutter_image_compress/example/.gitignore +++ b/packages/flutter_image_compress/example/.gitignore @@ -67,6 +67,11 @@ build/ **/ios/ServiceDefinitions.json **/ios/Runner/GeneratedPluginRegistrant.* +# Flutter iOS ephemeral (generated by toolchain) +**/ios/Flutter/ephemeral/ +**/ios/Flutter/ephemeral/flutter_lldb_helper.py +**/ios/Flutter/ephemeral/flutter_lldbinit + # Exceptions to above rules. !**/ios/**/default.mode1v3 !**/ios/**/default.mode2v3 diff --git a/packages/flutter_image_compress/example/ios/Flutter/AppFrameworkInfo.plist b/packages/flutter_image_compress/example/ios/Flutter/AppFrameworkInfo.plist index 9625e105..1dc6cf76 100644 --- a/packages/flutter_image_compress/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/flutter_image_compress/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 13.0 diff --git a/packages/flutter_image_compress/example/ios/Podfile b/packages/flutter_image_compress/example/ios/Podfile index 2c068c40..10f3c9b4 100644 --- a/packages/flutter_image_compress/example/ios/Podfile +++ b/packages/flutter_image_compress/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '12.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/flutter_image_compress/example/ios/Runner.xcodeproj/project.pbxproj b/packages/flutter_image_compress/example/ios/Runner.xcodeproj/project.pbxproj index 81df1230..b4841c7d 100644 --- a/packages/flutter_image_compress/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/flutter_image_compress/example/ios/Runner.xcodeproj/project.pbxproj @@ -165,7 +165,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -216,6 +216,7 @@ files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -269,7 +270,6 @@ "${BUILT_PRODUCTS_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework", "${BUILT_PRODUCTS_DIR}/flutter_image_compress_common/flutter_image_compress_common.framework", "${BUILT_PRODUCTS_DIR}/libwebp/libwebp.framework", - "${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -278,7 +278,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImageWebPCoder.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_image_compress_common.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libwebp.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -365,7 +364,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -412,7 +411,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/packages/flutter_image_compress/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/flutter_image_compress/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 82f4dcbb..e59cf996 100644 --- a/packages/flutter_image_compress/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/flutter_image_compress/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ @@ -46,11 +47,13 @@ buildConfiguration = "Release" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/packages/flutter_image_compress/example/lib/main/main_io.dart b/packages/flutter_image_compress/example/lib/main/main_io.dart index a3dd4b84..b7722dca 100644 --- a/packages/flutter_image_compress/example/lib/main/main_io.dart +++ b/packages/flutter_image_compress/example/lib/main/main_io.dart @@ -250,9 +250,9 @@ class _MyAppState extends State { } /// The example for compressing heic format. - /// + /// /// Convert jpeg to heic format, and then convert heic to jpg format. - /// + /// /// Show the file path and size in the console. void _compressHeicExample() async { print('start compress'); diff --git a/packages/flutter_image_compress/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/flutter_image_compress/example/macos/Flutter/GeneratedPluginRegistrant.swift index f7ed0d87..727ccf4b 100644 --- a/packages/flutter_image_compress/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/flutter_image_compress/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,9 +6,7 @@ import FlutterMacOS import Foundation import flutter_image_compress_macos -import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin")) - PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/packages/flutter_image_compress/example/pubspec.yaml b/packages/flutter_image_compress/example/pubspec.yaml index 6d744ac5..f572afb8 100644 --- a/packages/flutter_image_compress/example/pubspec.yaml +++ b/packages/flutter_image_compress/example/pubspec.yaml @@ -4,7 +4,8 @@ version: 1.0.0+1 publish_to: none environment: - sdk: '>=2.19.0 <3.0.0' + # Flutter now uses Dart 3; keep this example compatible with current toolchains. + sdk: '>=3.0.0 <4.0.0' flutter: '>=2.0.0' dependencies: @@ -16,10 +17,14 @@ dependencies: # flutter_image_compress_web: # path: ../../flutter_image_compress_web path_provider: ^2.0.0 - path_provider_ohos: - git: - url: https://gitee.com/openharmony-sig/flutter_packages.git - path: packages/path_provider/path_provider_ohos + + # NOTE: The OHOS implementation lives in a separate repo and is intentionally + # not pulled in by default to keep `flutter pub get` and CI analysis offline-friendly. + # If you need OHOS in the example, add `path_provider_ohos` back locally. + # path_provider_ohos: + # git: + # url: https://gitee.com/openharmony-sig/flutter_packages.git + # path: packages/path_provider/path_provider_ohos flutter: uses-material-design: true diff --git a/packages/flutter_image_compress_common/ios/Classes/SYPictureMetadata/SYMetadata.h b/packages/flutter_image_compress_common/ios/Classes/SYPictureMetadata/SYMetadata.h index 64552597..d78b3bd4 100755 --- a/packages/flutter_image_compress_common/ios/Classes/SYPictureMetadata/SYMetadata.h +++ b/packages/flutter_image_compress_common/ios/Classes/SYPictureMetadata/SYMetadata.h @@ -1,12 +1,12 @@ // // SYMetadata.h -// SYPictureMetadataExample -// -// Created by Stan Chevallier on 12/13/12. -// Copyright (c) 2012 Syan. All rights reserved. // #import + +// Forward declare to reduce header coupling; Photos is imported in SYMetadata.m. +@class PHAsset; + #import "SYMetadataTIFF.h" #import "SYMetadataGIF.h" #import "SYMetadataJFIF.h" @@ -26,11 +26,6 @@ #import "SYMetadataDNG.h" #import "SYMetadataExifAux.h" -// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/index.html -// http://www.exiv2.org/tags.html - -@class ALAsset; - @interface SYMetadata : SYMetadataBase @property SYMETADATA_PROPERTY_COPY NSDictionary *originalDictionary; @@ -54,7 +49,8 @@ @property SYMETADATA_PROPERTY_STRONG SYMetadataDNG *metadataDNG; @property SYMETADATA_PROPERTY_STRONG SYMetadataExifAux *metadataExifAux; -// we don't know how to parse those, so we juste give access to them +// Fallback +// Unparsed vendor-specific dictionaries exposed as-is for consumers (fallback for unknown/proprietary metadata). @property SYMETADATA_PROPERTY_COPY NSDictionary *metadataApple; @property SYMETADATA_PROPERTY_COPY NSDictionary *metadataPictureStyle; @@ -72,12 +68,27 @@ @property (nonatomic, copy, readonly) NSString *profileName; + (instancetype)metadataWithDictionary:(NSDictionary *)dictionary; -+ (instancetype)metadataWithAsset:(ALAsset *)asset __TVOS_PROHIBITED; -+ (instancetype)metadataWithAssetURL:(NSURL *)assetURL __TVOS_PROHIBITED; + +/// Photos-framework based metadata extraction. ++ (instancetype)metadataWithPHAsset:(PHAsset *)asset; + +/// Deprecated legacy API (ALAsset-era). Prefer `metadataWithPHAsset:`. +/// +/// This keeps source compatibility with older versions of this library without +/// referencing the removed AssetsLibrary framework types. ++ (instancetype)metadataWithAsset:(id)asset __attribute__((deprecated("Use metadataWithPHAsset: instead."))); + +/// Deprecated legacy API (ALAsset URL-era). Prefer `metadataWithPHAsset:`. ++ (instancetype)metadataWithAssetURL:(NSURL *)assetURL __attribute__((deprecated("Use metadataWithPHAsset: instead."))); + + (instancetype)metadataWithFileURL:(NSURL *)fileURL; + (instancetype)metadataWithImageData:(NSData *)imageData; -+ (NSDictionary *)dictionaryWithAssetURL:(NSURL *)assetURL __TVOS_PROHIBITED; +/// Photos-framework based metadata dictionary extraction. ++ (NSDictionary *)dictionaryWithPHAsset:(PHAsset *)asset; + +/// Deprecated legacy API (ALAsset URL-era). Prefer `dictionaryWithPHAsset:`. ++ (NSDictionary *)dictionaryWithAssetURL:(NSURL *)assetURL __attribute__((deprecated("Use dictionaryWithPHAsset: instead."))); + (NSData *)dataWithImageData:(NSData *)imageData andMetadata:(SYMetadata *)metadata; diff --git a/packages/flutter_image_compress_common/ios/Classes/SYPictureMetadata/SYMetadata.m b/packages/flutter_image_compress_common/ios/Classes/SYPictureMetadata/SYMetadata.m index 7bb9ac0c..443d8b26 100755 --- a/packages/flutter_image_compress_common/ios/Classes/SYPictureMetadata/SYMetadata.m +++ b/packages/flutter_image_compress_common/ios/Classes/SYPictureMetadata/SYMetadata.m @@ -3,16 +3,13 @@ // SYPictureMetadataExample // // Created by Stan Chevallier on 12/13/12. -// Copyright (c) 2012 Syan. All rights reserved. +// Updated by Alfin (2025) – Migrated to PHAsset / Photos framework // #import #import "SYMetadata.h" #import "NSDictionary+SY.h" - -#if !TARGET_OS_TV -#import -#endif +#import #define SYKeyForMetadata(name) NSStringFromSelector(@selector(metadata##name)) #define SYDictionaryForMetadata(name) SYPaste(SYPaste(kCGImageProperty,name),Dictionary) @@ -32,56 +29,124 @@ + (instancetype)metadataWithDictionary:(NSDictionary *)dictionary { if (!dictionary) return nil; - + NSError *error; - + SYMetadata *instance = [MTLJSONAdapter modelOfClass:self.class fromJSONDictionary:dictionary error:&error]; - + if (instance) instance->_originalDictionary = dictionary; - + if (error) NSLog(@"--> Error creating %@ object: %@", NSStringFromClass(self.class), error); - + return instance; } -+ (instancetype)metadataWithAsset:(ALAsset *)asset +/// Shared helper: synchronously fetch image data for a PHAsset and extract its metadata dictionary. +/// +/// Notes: +/// - Enables iCloud downloads (networkAccessAllowed). +/// - Avoids calling Photos synchronous requests on the main thread to prevent UI jank/deadlocks. +static NSDictionary *_Nullable SYMetadataCopyImagePropertiesFromPHAsset(PHAsset *asset) { + if (!asset) return nil; + + PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init]; + options.version = PHImageRequestOptionsVersionCurrent; + options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; + options.networkAccessAllowed = YES; + + __block NSData *imageData = nil; + + void (^requestBlock)(void) = ^{ + // If we're off-main-thread, it's safe to use synchronous=YES. + options.synchronous = ![NSThread isMainThread]; + + [[PHImageManager defaultManager] requestImageDataAndOrientationForAsset:asset + options:options + resultHandler:^(NSData * _Nullable data, + NSString * _Nullable dataUTI, + CGImagePropertyOrientation orientation, + NSDictionary * _Nullable info) { + imageData = data; + }]; + }; + + if ([NSThread isMainThread]) { + // Don't block Photos on the main thread; use async + semaphore. + dispatch_semaphore_t sema = dispatch_semaphore_create(0); + options.synchronous = NO; + [[PHImageManager defaultManager] requestImageDataAndOrientationForAsset:asset + options:options + resultHandler:^(NSData * _Nullable data, + NSString * _Nullable dataUTI, + CGImagePropertyOrientation orientation, + NSDictionary * _Nullable info) { + imageData = data; + dispatch_semaphore_signal(sema); + }]; + // Wait a bounded time to avoid potential deadlocks. + dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(30 * NSEC_PER_SEC))); + } else { + requestBlock(); + } + + if (!imageData.length) return nil; + + CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL); + if (!source) return nil; + + NSDictionary *metadataDict = (__bridge_transfer NSDictionary *) + CGImageSourceCopyPropertiesAtIndex(source, 0, NULL); + CFRelease(source); + + return metadataDict; +} + ++ (instancetype)metadataWithPHAsset:(PHAsset *)asset { -#if !TARGET_OS_TV - ALAssetRepresentation *representation = [asset defaultRepresentation]; - return [self metadataWithDictionary:[representation metadata]]; -#else + NSDictionary *metadataDict = SYMetadataCopyImagePropertiesFromPHAsset(asset); + return metadataDict ? [SYMetadata metadataWithDictionary:metadataDict] : nil; +} + ++ (instancetype)metadataWithAsset:(id)asset +{ + // Best-effort source-compat wrapper: if caller already provides a PHAsset, forward to it. + if ([asset isKindOfClass:[PHAsset class]]) { + return [self metadataWithPHAsset:(PHAsset *)asset]; + } return nil; -#endif } + (instancetype)metadataWithAssetURL:(NSURL *)assetURL { - NSDictionary *dictionary = [self dictionaryWithAssetURL:assetURL]; - return [self metadataWithDictionary:dictionary]; + if (!assetURL) return nil; + + PHFetchResult *fetch = [PHAsset fetchAssetsWithALAssetURLs:@[assetURL] options:nil]; + PHAsset *asset = fetch.firstObject; + return asset ? [self metadataWithPHAsset:asset] : nil; } + (instancetype)metadataWithFileURL:(NSURL *)fileURL { if (!fileURL) return nil; - + CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)fileURL, NULL); if (source == NULL) return nil; - + NSDictionary *dictionary; - + NSDictionary *options = @{(NSString *)kCGImageSourceShouldCache:@(NO)}; CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(source, 0, (__bridge CFDictionaryRef)options); if (properties) { dictionary = (__bridge NSDictionary*)properties; CFRelease(properties); } - + CFRelease(source); - + return [self metadataWithDictionary:dictionary]; } @@ -89,22 +154,22 @@ + (instancetype)metadataWithImageData:(NSData *)imageData { if (!imageData.length) return nil; - + CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL); if (source == NULL) return nil; - + NSDictionary *dictionary; - + NSDictionary *options = @{(NSString *)kCGImageSourceShouldCache:@(NO)}; CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(source, 0, (__bridge CFDictionaryRef)options); if (properties) { dictionary = (__bridge NSDictionary*)properties; CFRelease(properties); } - + CFRelease(source); - + return [self metadataWithDictionary:dictionary]; } @@ -118,57 +183,45 @@ + (NSData *)dataWithImageData:(NSData *)imageData andMetadata:(SYMetadata *)meta NSLog(@"Error: Could not create image source"); return nil; } - + CFStringRef sourceImageType = CGImageSourceGetType(source); - + // create a new data object and write the new image into it NSMutableData *data = [NSMutableData data]; CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)data, sourceImageType, 1, NULL); - + if (!destination) { NSLog(@"Error: Could not create image destination"); CFRelease(source); return nil; } - - // add the image contained in the image source to the destination, overidding the old metadata with our modified metadata + + // add the image contained in the image source to the destination, overriding the old metadata with our modified metadata CGImageDestinationAddImageFromSource(destination, source, 0, (__bridge CFDictionaryRef)metadata.generatedDictionary); BOOL success = CGImageDestinationFinalize(destination); - + if (!success) NSLog(@"Error: Could not create data from image destination"); - + CFRelease(destination); CFRelease(source); - + return (success ? data : nil); } #pragma mark - Getting metadata ++ (NSDictionary *)dictionaryWithPHAsset:(PHAsset *)asset +{ + return SYMetadataCopyImagePropertiesFromPHAsset(asset); +} + + (NSDictionary *)dictionaryWithAssetURL:(NSURL *)assetURL { -#if !TARGET_OS_TV - __block ALAsset *assetAtUrl = nil; - ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init]; - - dispatch_semaphore_t sema = dispatch_semaphore_create(0); - [library assetForURL:assetURL resultBlock:^(ALAsset *asset) { - assetAtUrl = asset; - dispatch_semaphore_signal(sema); - } failureBlock:^(NSError *error) { - dispatch_semaphore_signal(sema); - }]; - dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); - - if (!assetAtUrl) - return nil; - - ALAssetRepresentation *representation = [assetAtUrl defaultRepresentation]; - return [representation metadata]; -#else - return nil; -#endif + if (!assetURL) return nil; + PHFetchResult *fetch = [PHAsset fetchAssetsWithALAssetURLs:@[assetURL] options:nil]; + PHAsset *asset = fetch.firstObject; + return asset ? [self dictionaryWithPHAsset:asset] : nil; } #pragma mark - Mapping