-
Notifications
You must be signed in to change notification settings - Fork 5
File copy and image operations #56
Hi
My app captures images with the camera and I'm looking to use native workmanager to handle some processing (crop, resize, remote sync are the first I need to tackle)
I have little experience with the native platforms (I'm targeting iOS only for now), but my understanding is that there exists a file-copy API which functions via a copy-on-write style operation, in contrast to dart's File.copy which duplicates the data to accomplish the operation. Does FileSystemCopyWorker make use of this style on iOS? The camera package writes to temp file space and I need it to like in a more appropriate long-term file space, and am wondering if I might see a load-decreasing improvement by delegating to a worker. A copy vs move is slightly advantageous in my scenario as I display the image immediately and handed across routes in the same time frame so it reduces complexity if the image can live in both places during that short period of time.
In regards to image processing, is there a way to get an image's dimensions as a step in a pipeline prior to performing a crop? The camera package allows for specification of various capture "resolutions", but they're not strictly defined in terms of the pixel sizes of the resulting file (https://pub.dev/documentation/camera/latest/camera/ResolutionPreset.html). The package does include APIs for inspecting the dimensions after capture, so it's not a big pain point (I'm cropping the center square from a 9:16 capture), but it would be something I'd prefer to defer to the worker if possible.
Thanks for the work you've been putting into this package. I've been preparing to use workmanager for my implementation and came across this by chance and am delighted I found it. It looks nice all around and I'm definitely a fan of the generator / strong-types effort you worked through. I'm excited to dig in more.
Cheers
All reactions
-
❤️ 1 -
👀 1
Replies: 2 comments
hey! thanks for the kind words, and for the detailed writeup, both are great questions.
- copy-on-write: yeah your understanding is right, but the "why" is a bit more specific than "apple does cow automatically". FileSystemWorker's copy op calls
FileManager.copyItem(at:to:)under the hood (FileSystemWorker.swift:495), and Foundation adds theCOPYFILE_CLONEflag when it calls intocopyfile()internally — thats what actually triggers the clone. I actually went and measured it just now on a 500mb file (same apfs volume):FileManager.copyItemtook ~0.0003s and used ~4kb of disk, vs a rawcopyfile()call without that flag which took 0.65s and wrote the full 500mb.
funny enough i also checked dart's own sdk source (runtime/bin/file_macos.cc) — File.copy() on ios/macos calls that exact same copyfile() syscall, just without COPYFILE_CLONE. so it's not that dart does something slow or manual, it calls the same os primitive, just missing the one flag that makes it a clone instead of a full copy. so delegating to FileSystemWorker does get you the free clone, plus it skips flutter engine boot entirely.
one caveat worth mentioning: clone only works same-volume. tmp/, Documents/, and Library/Caches/ are all inside your app sandbox container so you're fine there, and I double checked our path validator explicitly allows all 3.
- image dims before crop: yep,
originalWidth/originalHeightcome back in the result data always (ImageProcessWorker.swift:108-114,CGImageSourceCopyPropertiesAtIndexon ios /inJustDecodeBoundson android), no pixel decode for that step. where it falls short of what you want tho — there's currently no way to feed those dims into a computed center-squarecropRectin one call, or across chain steps (chains do support{{task.output_key}}between steps but that only works on string config fields right now,cropRect's x/y/w/h are ints so it cant reach those anyway).
so for the 9:16 -> center square case, doing the math in dart after capture (like you're already doing) is genuinely the right move today, its cheap and the camera package gives you the dims anyway.
that said i think you found a real gap — a cropAspectRatio / center-crop mode that resolves against the native probed dimensions is a good idea, ill add it to the backlog. if you want to take a stab at a PR happy to point you at the two worker files, otherwise ill get to it eventually 👍
thanks for putting this together so thoroughly btw, appreciate it
All reactions
quick follow-up on the chain-placeholder gap I mentioned above ("cropRect's x/y/w/h are ints so it cant reach those anyway") — turns out that was one symptom of a bigger bug, filed it as #57 and just shipped the fix in v1.4.5.
root cause was the whole-match placeholder path always stringified whatever it substituted, so even if you pointed a numeric field at {{task.output_key}} the native side got a JSON string where it expected an int and rejected it. also found (separately) that step results were stored under flat unprefixed keys on iOS while lookups used the compound taskId.key string, so most substitutions never resolved at all, and android had zero substitution support period.
as of 1.4.5, a whole-match placeholder (the entire config value is just {{task.output_key}}, nothing else in the string) now resolves to the original typed value instead of a string, so this works now:
await NativeWorkManager.beginWith( TaskRequest( id: 'probe', worker: NativeWorker.imageProcess(inputPath: p, outputPath: mid, maxWidth: 100, maxHeight: 100), ), ) .then(TaskRequest( id: 'crop', worker: NativeWorker.custom( className: 'ImageProcessWorker', input: { 'inputPath': mid, 'outputPath': out, 'cropRect': { 'x': 0, 'y': 0, 'width': '{{probe.originalWidth}}', 'height': '{{probe.originalWidth}}', // center square off the probed width }, }, ), )) .enqueue();
still no built-in cropAspectRatio/center-crop mode — that backlog item stands — but the raw mechanism to wire a probed dimension into a numeric config field now actually works, on both platforms. full writeup in the issue if you want the gory details: #57