feat: Support web
This commit is contained in:
parent
8b47d1295b
commit
c0f8076b56
26
README.md
26
README.md
|
|
@ -15,9 +15,12 @@ A high-performance Flutter plugin for cross-platform image format conversion usi
|
||||||
| iOS | 14.0 | ImageIO (CoreFoundation, CoreGraphics) |
|
| iOS | 14.0 | ImageIO (CoreFoundation, CoreGraphics) |
|
||||||
| macOS | 10.15 | ImageIO (CoreFoundation, CoreGraphics) |
|
| macOS | 10.15 | ImageIO (CoreFoundation, CoreGraphics) |
|
||||||
| Android | 7 | BitmapFactory, Bitmap compression |
|
| Android | 7 | BitmapFactory, Bitmap compression |
|
||||||
| Web | N/A | Not supported |
|
| Web | - | Canvas API |
|
||||||
|
|
||||||
on Android, HEIC input is supported on Android 9+ but HEIC output is not supported.
|
**Note:**
|
||||||
|
- On iOS and macOS, WebP input is supported but WebP output is not supported.
|
||||||
|
- On Android, HEIC input is supported on Android 9+ but HEIC output is not supported.
|
||||||
|
- On Web, HEIC is not supported.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
|
|
@ -137,3 +140,22 @@ The Android implementation uses [BitmapFactory](https://developer.android.com/re
|
||||||
**Key Limitations:**
|
**Key Limitations:**
|
||||||
- HEIC can be read (input only) but cannot be written (output format not supported)
|
- HEIC can be read (input only) but cannot be written (output format not supported)
|
||||||
- Requires Android 9+ for full HEIC support
|
- Requires Android 9+ for full HEIC support
|
||||||
|
|
||||||
|
### Web Implementation
|
||||||
|
|
||||||
|
The Web implementation uses the [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) for image conversion:
|
||||||
|
|
||||||
|
1. **Decoding**: `HTMLImageElement` loads image data via Blob URL
|
||||||
|
2. **Rendering**: `CanvasRenderingContext2D.drawImage` renders the image to canvas
|
||||||
|
3. **Encoding**: `HTMLCanvasElement.toBlob` encodes to target format
|
||||||
|
4. **Quality**: Supports quality parameter for JPEG and WebP (0.0-1.0 scale)
|
||||||
|
|
||||||
|
**Key Steps:**
|
||||||
|
- `Blob` and `URL.createObjectURL`: Create temporary URL from input bytes
|
||||||
|
- `HTMLImageElement.onLoad`: Wait for image to load asynchronously
|
||||||
|
- `canvas.width/height = img.width/height`: Set canvas size to match image dimensions
|
||||||
|
- `canvas.toBlob`: Convert canvas content to target format
|
||||||
|
|
||||||
|
**Key Limitations:**
|
||||||
|
- HEIC format is not supported on Web platform
|
||||||
|
- Output format depends on browser support (JPEG and PNG are universally supported, WebP is widely supported)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: "66dd93f9a27ffe2a9bfc8297506ce066ff51265f"
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
|
||||||
|
base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
|
||||||
|
- platform: android
|
||||||
|
create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
|
||||||
|
base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
|
||||||
|
- platform: ios
|
||||||
|
create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
|
||||||
|
base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
|
||||||
|
- platform: web
|
||||||
|
create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
|
||||||
|
base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
|
|
@ -6,7 +6,7 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "dr1009.com.image_ffi_example"
|
namespace = "com.example.image_ffi_example"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ android {
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId = "dr1009.com.image_ffi_example"
|
applicationId = "com.example.image_ffi_example"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
minSdk = flutter.minSdkVersion
|
minSdk = flutter.minSdkVersion
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package dr1009.com.image_ffi_example
|
package com.example.image_ffi_example
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
|
@ -11,10 +11,10 @@
|
||||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
|
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
|
@ -48,6 +48,7 @@
|
||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
|
|
@ -56,7 +57,6 @@
|
||||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
|
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
|
@ -146,9 +146,6 @@
|
||||||
productType = "com.apple.product-type.bundle.unit-test";
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
};
|
};
|
||||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
packageProductDependencies = (
|
|
||||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
|
||||||
);
|
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
|
@ -164,6 +161,9 @@
|
||||||
dependencies = (
|
dependencies = (
|
||||||
);
|
);
|
||||||
name = Runner;
|
name = Runner;
|
||||||
|
packageProductDependencies = (
|
||||||
|
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
||||||
|
);
|
||||||
productName = Runner;
|
productName = Runner;
|
||||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
|
|
@ -172,9 +172,6 @@
|
||||||
|
|
||||||
/* Begin PBXProject section */
|
/* Begin PBXProject section */
|
||||||
97C146E61CF9000F007C117D /* Project object */ = {
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
packageReferences = (
|
|
||||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
|
|
||||||
);
|
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = YES;
|
BuildIndependentTargetsInParallel = YES;
|
||||||
|
|
@ -200,6 +197,9 @@
|
||||||
Base,
|
Base,
|
||||||
);
|
);
|
||||||
mainGroup = 97C146E51CF9000F007C117D;
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
|
packageReferences = (
|
||||||
|
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
|
||||||
|
);
|
||||||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
|
|
@ -378,7 +378,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = dr1009.com.imageFfiExample;
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.imageFfiExample;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|
@ -394,7 +394,7 @@
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = dr1009.com.imageFfiExample.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.imageFfiExample.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
|
@ -411,7 +411,7 @@
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = dr1009.com.imageFfiExample.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.imageFfiExample.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
|
@ -426,7 +426,7 @@
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = dr1009.com.imageFfiExample.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.imageFfiExample.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
|
@ -557,7 +557,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = dr1009.com.imageFfiExample;
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.imageFfiExample;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
|
@ -579,7 +579,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = dr1009.com.imageFfiExample;
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.imageFfiExample;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|
@ -621,12 +621,14 @@
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCLocalSwiftPackageReference section */
|
/* Begin XCLocalSwiftPackageReference section */
|
||||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
|
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
|
||||||
isa = XCLocalSwiftPackageReference;
|
isa = XCLocalSwiftPackageReference;
|
||||||
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
|
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
|
||||||
};
|
};
|
||||||
/* End XCLocalSwiftPackageReference section */
|
/* End XCLocalSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
|
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>Image Ffi</string>
|
<string>Image Ffi Example</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 917 B |
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
|
|
@ -0,0 +1,38 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!--
|
||||||
|
If you are serving your web app in a path other than the root, change the
|
||||||
|
href value below to reflect the base path you are serving from.
|
||||||
|
|
||||||
|
The path provided below has to start and end with a slash "/" in order for
|
||||||
|
it to work correctly.
|
||||||
|
|
||||||
|
For more details:
|
||||||
|
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||||
|
|
||||||
|
This is a placeholder for base href that will be replaced by the value of
|
||||||
|
the `--base-href` argument provided to `flutter build`.
|
||||||
|
-->
|
||||||
|
<base href="$FLUTTER_BASE_HREF">
|
||||||
|
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||||
|
<meta name="description" content="A new Flutter project.">
|
||||||
|
|
||||||
|
<!-- iOS meta tags & icons -->
|
||||||
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="image_ffi_example">
|
||||||
|
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||||
|
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||||
|
|
||||||
|
<title>image_ffi_example</title>
|
||||||
|
<link rel="manifest" href="manifest.json">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="flutter_bootstrap.js" async></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"name": "image_ffi_example",
|
||||||
|
"short_name": "image_ffi_example",
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0175C2",
|
||||||
|
"theme_color": "#0175C2",
|
||||||
|
"description": "A new Flutter project.",
|
||||||
|
"orientation": "portrait-primary",
|
||||||
|
"prefer_related_applications": false,
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-maskable-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-maskable-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -3,10 +3,11 @@ library;
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:image_ffi/src/image_converter_android.dart';
|
|
||||||
import 'package:image_ffi/src/image_converter_darwin.dart';
|
|
||||||
import 'package:image_ffi/src/image_converter_platform_interface.dart';
|
import 'package:image_ffi/src/image_converter_platform_interface.dart';
|
||||||
import 'package:image_ffi/src/output_format.dart';
|
import 'package:image_ffi/src/output_format.dart';
|
||||||
|
import 'package:image_ffi/src/android/shared.dart';
|
||||||
|
import 'package:image_ffi/src/darwin/shared.dart';
|
||||||
|
import 'package:image_ffi/src/web/shared.dart';
|
||||||
|
|
||||||
export 'src/output_format.dart';
|
export 'src/output_format.dart';
|
||||||
|
|
||||||
|
|
@ -99,11 +100,11 @@ class _ConvertRequest {
|
||||||
/// Returns the platform-specific converter instance.
|
/// Returns the platform-specific converter instance.
|
||||||
ImageConverterPlatform _getPlatformForTarget(TargetPlatform platform) {
|
ImageConverterPlatform _getPlatformForTarget(TargetPlatform platform) {
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
throw UnsupportedError('Image conversion is not supported on the web.');
|
return const ImageConverterWeb();
|
||||||
}
|
}
|
||||||
return switch (platform) {
|
return switch (platform) {
|
||||||
TargetPlatform.android => ImageConverterAndroid(),
|
TargetPlatform.android => const ImageConverterAndroid(),
|
||||||
TargetPlatform.iOS || TargetPlatform.macOS => ImageConverterDarwin(),
|
TargetPlatform.iOS || TargetPlatform.macOS => const ImageConverterDarwin(),
|
||||||
_ => throw UnsupportedError(
|
_ => throw UnsupportedError(
|
||||||
'Image conversion is not supported on this platform: $platform',
|
'Image conversion is not supported on this platform: $platform',
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ import 'package:jni/jni.dart';
|
||||||
/// - Native image decoding via BitmapFactory
|
/// - Native image decoding via BitmapFactory
|
||||||
/// - Efficient compression with quality adjustment
|
/// - Efficient compression with quality adjustment
|
||||||
final class ImageConverterAndroid implements ImageConverterPlatform {
|
final class ImageConverterAndroid implements ImageConverterPlatform {
|
||||||
|
const ImageConverterAndroid();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Uint8List> convert({
|
Future<Uint8List> convert({
|
||||||
required Uint8List inputData,
|
required Uint8List inputData,
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export 'stub.dart'
|
||||||
|
if (dart.library.io) 'native.dart'
|
||||||
|
if (dart.library.js_interop) 'stub.dart';
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:image_ffi/src/image_converter_platform_interface.dart';
|
||||||
|
import 'package:image_ffi/src/output_format.dart';
|
||||||
|
|
||||||
|
final class ImageConverterAndroid implements ImageConverterPlatform {
|
||||||
|
const ImageConverterAndroid();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Uint8List> convert({
|
||||||
|
required Uint8List inputData,
|
||||||
|
OutputFormat format = OutputFormat.jpeg,
|
||||||
|
int quality = 100,
|
||||||
|
}) async => throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,8 @@ import 'package:objective_c/objective_c.dart';
|
||||||
/// - In-memory processing
|
/// - In-memory processing
|
||||||
/// - Adjustable JPEG/WebP quality for size optimization
|
/// - Adjustable JPEG/WebP quality for size optimization
|
||||||
final class ImageConverterDarwin implements ImageConverterPlatform {
|
final class ImageConverterDarwin implements ImageConverterPlatform {
|
||||||
|
const ImageConverterDarwin();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Uint8List> convert({
|
Future<Uint8List> convert({
|
||||||
required Uint8List inputData,
|
required Uint8List inputData,
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export 'stub.dart'
|
||||||
|
if (dart.library.io) 'native.dart'
|
||||||
|
if (dart.library.js_interop) 'stub.dart';
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:image_ffi/src/image_converter_platform_interface.dart';
|
||||||
|
import 'package:image_ffi/src/output_format.dart';
|
||||||
|
|
||||||
|
final class ImageConverterDarwin implements ImageConverterPlatform {
|
||||||
|
const ImageConverterDarwin();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Uint8List> convert({
|
||||||
|
required Uint8List inputData,
|
||||||
|
OutputFormat format = OutputFormat.jpeg,
|
||||||
|
int quality = 100,
|
||||||
|
}) async => throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
@ -3,17 +3,18 @@
|
||||||
/// Specifies the target format when converting images.
|
/// Specifies the target format when converting images.
|
||||||
///
|
///
|
||||||
/// **Format Support:**
|
/// **Format Support:**
|
||||||
/// | Format | iOS/macOS | Android |
|
/// | Format | iOS/macOS | Android | Web |
|
||||||
/// |--------|-----------|---------|
|
/// |--------|-----------|---------| ----|
|
||||||
/// | jpeg | ✓ | ✓ |
|
/// | jpeg | ✓ | ✓ | ✓ |
|
||||||
/// | png | ✓ | ✓ |
|
/// | png | ✓ | ✓ | ✓ |
|
||||||
/// | webp | | ✓ |
|
/// | webp | | ✓ | ✓ |
|
||||||
/// | heic | ✓ | |
|
/// | heic | ✓ | | |
|
||||||
///
|
///
|
||||||
/// **Notes:**
|
/// **Notes:**
|
||||||
/// - [jpeg]: Good compression with adjustable quality
|
/// - [jpeg]: Good compression with adjustable quality
|
||||||
/// - [png]: Lossless compression, supports transparency
|
/// - [png]: Lossless compression, supports transparency
|
||||||
/// - [webp]: Modern format with better compression than JPEG
|
/// - [webp]: Modern format with better compression than JPEG
|
||||||
|
/// - [heic]: High Efficiency Image Format, not supported on Android
|
||||||
enum OutputFormat {
|
enum OutputFormat {
|
||||||
/// JPEG format (.jpg, .jpeg)
|
/// JPEG format (.jpg, .jpeg)
|
||||||
/// Lossy compression, suitable for photos
|
/// Lossy compression, suitable for photos
|
||||||
|
|
@ -28,6 +29,6 @@ enum OutputFormat {
|
||||||
webp,
|
webp,
|
||||||
|
|
||||||
/// HEIC format (.heic)
|
/// HEIC format (.heic)
|
||||||
/// High Efficiency Image Format (not supported on Android)
|
/// High Efficiency Image Format (not supported on Android and Web)
|
||||||
heic,
|
heic,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export 'stub.dart'
|
||||||
|
if (dart.library.io) 'stub.dart'
|
||||||
|
if (dart.library.js_interop) 'web.dart';
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:image_ffi/src/image_converter_platform_interface.dart';
|
||||||
|
import 'package:image_ffi/src/output_format.dart';
|
||||||
|
|
||||||
|
final class ImageConverterWeb implements ImageConverterPlatform {
|
||||||
|
const ImageConverterWeb();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Uint8List> convert({
|
||||||
|
required Uint8List inputData,
|
||||||
|
OutputFormat format = OutputFormat.jpeg,
|
||||||
|
int quality = 100,
|
||||||
|
}) async => throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:js_interop';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:image_ffi/src/image_converter_platform_interface.dart';
|
||||||
|
import 'package:image_ffi/src/output_format.dart';
|
||||||
|
import 'package:web/web.dart';
|
||||||
|
|
||||||
|
/// Web image converter using Canvas API.
|
||||||
|
///
|
||||||
|
/// Implements image conversion for web platforms using the HTML5 Canvas API
|
||||||
|
/// for decoding and encoding images in the browser.
|
||||||
|
///
|
||||||
|
/// **Features:**
|
||||||
|
/// - Supports JPEG, PNG, WebP formats (browser-dependent)
|
||||||
|
/// - HEIC format not supported on Web
|
||||||
|
/// - Adjustable quality for JPEG and WebP compression
|
||||||
|
/// - Asynchronous image loading and encoding
|
||||||
|
///
|
||||||
|
/// **API Stack:**
|
||||||
|
/// - `HTMLImageElement`: Load and decode input image via Blob URL
|
||||||
|
/// - `CanvasRenderingContext2D.drawImage`: Render image to canvas
|
||||||
|
/// - `HTMLCanvasElement.toBlob`: Encode canvas to target format
|
||||||
|
///
|
||||||
|
/// **Limitations:**
|
||||||
|
/// - HEIC not supported (throws UnsupportedError)
|
||||||
|
/// - Output format support depends on browser capabilities
|
||||||
|
/// - JPEG and PNG are universally supported
|
||||||
|
/// - WebP is widely supported in modern browsers
|
||||||
|
///
|
||||||
|
/// **Performance:**
|
||||||
|
/// - Browser-native image decoding and encoding
|
||||||
|
/// - In-memory processing with Blob/ArrayBuffer
|
||||||
|
/// - Quality parameter controls compression ratio
|
||||||
|
final class ImageConverterWeb implements ImageConverterPlatform {
|
||||||
|
const ImageConverterWeb();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Uint8List> convert({
|
||||||
|
required Uint8List inputData,
|
||||||
|
OutputFormat format = OutputFormat.jpeg,
|
||||||
|
int quality = 100,
|
||||||
|
}) async {
|
||||||
|
final img = HTMLImageElement();
|
||||||
|
final decodeCompeleter = Completer<void>();
|
||||||
|
|
||||||
|
final blob = Blob([inputData.toJS].toJS);
|
||||||
|
final url = URL.createObjectURL(blob);
|
||||||
|
img.onLoad.listen((_) => decodeCompeleter.complete());
|
||||||
|
img.onError.listen((e) {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
decodeCompeleter.completeError('Failed to load image: $e');
|
||||||
|
});
|
||||||
|
img.src = url;
|
||||||
|
await decodeCompeleter.future;
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
final canvas = HTMLCanvasElement();
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
final ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
|
||||||
|
final encodeCompleter = Completer<Blob>();
|
||||||
|
final type = switch (format) {
|
||||||
|
OutputFormat.jpeg => 'image/jpeg',
|
||||||
|
OutputFormat.png => 'image/png',
|
||||||
|
OutputFormat.webp => 'image/webp',
|
||||||
|
OutputFormat.heic => throw UnsupportedError(
|
||||||
|
'HEIC output format is not supported on Web.',
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
canvas.toBlob(
|
||||||
|
(Blob? blob) {
|
||||||
|
if (blob != null) {
|
||||||
|
encodeCompleter.complete(blob);
|
||||||
|
} else {
|
||||||
|
encodeCompleter.completeError(
|
||||||
|
'Failed to convert canvas to JPEG Blob.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}.toJS,
|
||||||
|
type,
|
||||||
|
(quality / 100).toJS,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = await encodeCompleter.future;
|
||||||
|
final arrayBuffer = await result.arrayBuffer().toDart;
|
||||||
|
return arrayBuffer.toDart.asUint8List();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ dependencies:
|
||||||
jni: ^0.15.2
|
jni: ^0.15.2
|
||||||
ffi: ^2.1.4
|
ffi: ^2.1.4
|
||||||
objective_c: ^9.2.1
|
objective_c: ^9.2.1
|
||||||
|
web: ^1.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue