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