Skip to content

Navigation Menu

Sign in
Sign up

File copy and image operations #56

ryanhanks-bestow started this conversation in General
Discussion options

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

You must be logged in to vote

Replies: 2 comments

Comment options

hey! thanks for the kind words, and for the detailed writeup, both are great questions.

  1. 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 the COPYFILE_CLONE flag when it calls into copyfile() internally — thats what actually triggers the clone. I actually went and measured it just now on a 500mb file (same apfs volume): FileManager.copyItem took ~0.0003s and used ~4kb of disk, vs a raw copyfile() 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.

  1. image dims before crop: yep, originalWidth/originalHeight come back in the result data always (ImageProcessWorker.swift:108-114, CGImageSourceCopyPropertiesAtIndex on ios / inJustDecodeBounds on 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-square cropRect in 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

You must be logged in to vote
0 replies
Comment options

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

You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet

AltStyle によって変換されたページ (->オリジナル) /