Repository navigation
Save in the #convert format to an extensionless destination - #150
Merged
Merged
Conversation
A destination without an extension, such as `/dev/fd/N`, ignored `#convert`: libvips raised, and ImageMagick wrote the source format. Save in the `#convert` format when the destination has no extension. Destinations with an extension are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Sep 28, 2026
flavorjones
added a commit
to basecamp/hotcell
that referenced
this pull request
Sep 30, 2026
* Require image_processing 2.2.0 and drop the transform rename image_processing 2.1.0 chose its saver from the destination's extension, so the transform operations encoded to a suffixed sibling of the extensionless scratch path and renamed it into place. Require image_processing 2.2.0, which saves in the `#convert` format to a destination without an extension, and write to the scratch path directly. On the ImageMagick toolchain, a format ImageMagick has no name for, such as `jfif`, now fails as `unreadable` instead of being written in the source's format. ref: janko/image_processing#150 [Fix #4] * Describe the image_processing 2.2.0 upgrade as an improvement The CHANGELOG entry described a known edge case that a separate fix will handle, and did not mention the rename every transform now saves.
This was referenced Sep 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation / Background
This is the output side of #147.
HotCell receives the destination as an open file descriptor, but it can't pass
/dev/fd/Nasdestination:, because the savers pick the format from the path's extension. So HotCell saves to a scratch file namedoutput.pngand then copies that file into the descriptor (transforming.rb). That is an extra write and read of every output, plus tmpfs space for the scratch file.Detail
When the destination has no extension, save in the
#convertformat.Vips.vips_foreign_find_save(".#{format}")and filter options asselect_valid_saver_optionsdoes. Options in the destination's[...]suffix still apply, as withwrite_to_file."#{format}:#{destination}". A format that isn't a bare word (for example, one containing a colon) raisesImageProcessing::Error, because ImageMagick would read the rest as part of the output path.Destinations with an extension are unchanged, even when the extension disagrees with
#convert. Pipelines without#convertare unchanged..saver(saver:)still takes precedence.Pipelinepassesformat:toProcessor.callonly in the new case, andProcessor.callpasses it tosave_imageas a third positional argument only in the new case, so existing custom processors are unaffected elsewhere.Additional information
When the destination has no extension,
ImageProcessing::Vips::Processor.save_imagenow calls the libvips save operation directly instead of callingVips::Image#write_to_file. It therefore skips the garbage collection thatwrite_to_fileruns after each save (Vips::Image#write_gc, which is private). The existing.saver(saver:)option already calls the save operation directly and skips it too..saver(saver:)doesn't solve this problem, because its value is a libvips save operation name rather than a file format, and it disables the filtering of unsupported saver options.Closes #149