-
-
Notifications
You must be signed in to change notification settings - Fork 699
tell buffer and target savers the file format #2499
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Currently, buffer and target savers are not told the format they should write. This is usually OK (the JPEG saver already knows it should write JPEG), but some savers can write several formats, and these currently need an extra parameter. For example: ```ruby buf = x.write_to_buffer ".bmp", format: "bmp" ``` The first ".bmp" gets libvips to pick magicksave, the second `format:` param is necessary to tell magicksave to write BMP. This patch makes `vips_image_write_to_buffer` and `vips_image_write_to_target` add an extra image metadata item called `format_string` which contains the string used to pick the write format. Savers can use this extra field (if present) to pick a default save format. In this case, the magick saver will extract the "bmp". See: libvips/ruby-vips#319
diff --git a/lib/vips/image.rb b/lib/vips/image.rb
index 1b800d1..6e0be6b 100644
--- a/lib/vips/image.rb
+++ b/lib/vips/image.rb
@@ -626,7 +626,12 @@ module Vips
saver = Vips.vips_foreign_find_save_buffer filename
raise Vips::Error if saver.nil?
- buffer = Vips::Operation.call saver, [self], opts, option_string
+ # note the format_string for savers
+ image = self.mutate do |x|
+ x.set_type! Vips::REFSTR_TYPE, "format_string", format_string
+ end
+
+ buffer = Vips::Operation.call saver, [image], opts, option_string
raise Vips::Error if buffer.nil?
write_gc All language bindings (eg. net-vips, pyvips) will need something similar. |
Test program to show the issue: require 'vips'
x = Vips::Image.new_from_file ARGV[0]
%w(dz hdr jpc jpt jp2 j2c j2k webp).each do |format|
begin
buf = x.write_to_buffer ".#{format}"
puts "#{format}: written #{buf.length} bytes"
rescue Vips::Error
puts "#{format}: buffer write not supported"
end
end
# imagemagick-based savers need to be told the format to write twice :(
%w(bmp gif).each do |format|
begin
buf = x.write_to_buffer ".#{format}", format: format
puts "#{format}: written #{buf.length} bytes"
rescue Vips::Error
puts "#{format}: buffer write not supported"
end
end
# libheif buffer savers need to be told the format too
buf = x.write_to_buffer ".heif", compression: "hevc"
puts "heic: written #{buf.length} bytes"
buf = x.write_to_buffer ".heif", compression: "av1"
puts "avif: written #{buf.length} bytes" RationaleI tried a couple of other fixes, hoping to find some way to pass the format down to the saver without needing changes to bindings, but failed :( The closest was getting This needs changes to bindings, but the changes are simple and safe, and will be forwards and backwards compatible. And there's no need for extra secret args to the save operations. TODO
|
Any thoughts, @kleisauke? This would affect net-vips. |
and a version bump see libvips/libvips#2499
and remove gif from the list of things that magick supports (since we have our own saver now)
Revised test prog: #!/usr/bin/ruby
require 'vips'
x = Vips::Image.new_from_file ARGV[0]
# don't try bmp ... we don't have magicksave_target
%w(heif avif ppm pfm pgm).each do |format|
target = Vips::Target.new_to_memory
x.write_to_target target, ".#{format}"
puts "#{format}: written #{target.get("blob").length} bytes"
end
%w(bmp heif avif ppm pfm pgm).each do |format|
buf = x.write_to_buffer ".#{format}"
puts "#{format}: written #{buf.length} bytes"
end With all the changes I see:
So it's correctly picking the target format from the suffix. |
Same for python: #!/usr/bin/python3
import sys
import pyvips
x = pyvips.Image.new_from_file(sys.argv[1])
for format in ["heif", "avif", "ppm", "pfm", "pgm"]:
target = pyvips.Target.new_to_memory()
x.write_to_target(target, f".{format}")
print(f"{format}: written {len(target.get('blob'))} bytes")
for format in ["bmp", "heif", "avif", "ppm", "pfm", "pgm"]:
buf = x.write_to_buffer(f".{format}")
print(f"{format}: written {len(buf)} bytes") Giving:
|
since we use kebab-case elsewhere for image metadata
OK, all done! I switched it to |
Seems to work.
FWIW, I managed to get this working with commit kleisauke@921922b. This would result in fewer checks within savers, and you can still write (for example): $ vips magicksave x.jpg x.bmp --format gif
$ head -c 6 x.bmp
GIF89a But indeed, it's a bit ugly since this option would also be present in all savers and loaders: $ vips gifload | grep format
format - Format hint, input gchararray I also thought about splitting these deviating savers into aliased GObject classes, for example:
But I'm not sure if this can be done without too much hassle. |
This can be done easily, see for example commit kleisauke@d415a9b. With that I see: $ vips copy x.jpg x.avif
$ vips copy x.jpg x.heic
$ vipsheader x.avif -f heif-compression
av1
$ vipsheader x.heic -f heif-compression
hevc I marked that subclass as "deprecated" to hide it from |
Oh, that's a good idea. Though it would be very wordy (as you say). I also thought of hiding the extra info inside the Something else I tried was changing It would still need changes to all the bindings though :( And the binding changes wouldn't work with older libvipses, so bindings would need to see which libvips version they had and alter behaviour. |
Oh nice! We'd need to make these I guess: VipsForeignSaveAvifBuffer So 7 new subclasses. We could possibly skip the Buffer versions (though not for magick), so we'd only need four new subclasses. The C pyvips and ruby-vips don't yet have this logic, but they probably should. |
Commit kleisauke@9b33fb9 adds the remaining subclasses. There are now a total of new 5 saver subclasses:
I've tested this with: $ vips copy x.jpg x.avif
$ vips copy x.jpg x.heic
$ vips copy x.jpg x.ppm
$ vips copy x.jpg x.pgm
$ vips copy x.jpg x.pbm
$ vips copy x.jpg x.pfm
$ vips copy x.jpg x.bmp
$ vipsheader x.avif -f heif-compression
av1
$ vipsheader x.heic -f heif-compression
hevc
$ head -c 2 x.ppm
P6
$ head -c 2 x.pgm
P5
$ head -c 2 x.pbm
P5
$ head -c 2 x.pfm
PF
$ head -c 2 x.bmp
BM |
Oh, CI is currently failing due to:
|
To ensure that these subclasses are also skipped in `cplusplus/gen-operators.py`.
let's get pyvips etc. to use the target API instead
Great! You might also be interested in these 2 commits: |
Sure, they look good. |
Commit kleisauke/net-vips@f0b7ab9 implements the "try to save with the target API first" approach in net-vips, this seems to work for me. While doing that, I noticed that sometimes the libvips/libvips/iofuncs/image.c Lines 2201 to 2203 in 1f321d3
libvips/libvips/iofuncs/image.c Lines 2687 to 2691 in 1f321d3
libvips/libvips/iofuncs/image.c Line 2753 in 1f321d3
Perhaps this mechanism for hiding errors can be removed? |
and fix up heif a bit too
in vips_image_write_to_buffer()
You're right that third one needs a freeze/thaw as well. Freeze/thaw is ugly and stupid :( It would be better if A cleaner solution would be to make eg. |
write_to_buffer will now attempt to use the new Target API before falling back to the old buffer system. More savers support target than buffer now. See libvips/libvips#2499
https://github.com/libvips/ruby-vips/tree/revise-buffer-save4 adds target-first to ruby-vips. I'll have a look at py as well. |
Nice! I mirrored some of that ruby-vips code with commit kleisauke/net-vips@b7d1fd6 in net-vips. I think we are almost done, the only missing piece is that if( vips_iscasepostfix( file->filename, ".pbm" ) ||
vips_iscasepostfix( file->filename, ".pgm" ) )
VIPS_SETSTR( ppm->format, "pgm" );
else if( vips_iscasepostfix( file->filename, ".pfm" ) )
VIPS_SETSTR( ppm->format, "pfm" ); (Similar to the logic in |
OK, py done as well! |
Oh you're right. Let's add an enum, all these string suffixes are a bit clunky. |
and add a @Format param to ppmsave
It now supports PBM (bitmap format) too! We'll need to regenerate the static parts of the bindings, and I guess eg. php-vips needs some work too, but that can all wait. This is a nice API cleanup! And good job on the stub-subclass idea, it's much neater. OK to merge? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a small comment regarding the .pbm
file extension, but otherwise this looks good to me!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I just saw some potentially redundant code too.
Co-authored-by: Kleis Auke Wolthuizen <github@kleisauke.nl>
Needed after PR libvips#2499.
Currently, buffer and target savers are not told the format they should
write.
This is usually OK (the JPEG saver already knows it should write JPEG),
but some savers can write several formats, and these currently need an
extra parameter.
For example:
The first ".bmp" gets libvips to pick magicksave, the second
format:
param is necessary to tell magicksave to write BMP.This patch makes
vips_image_write_to_buffer
andvips_image_write_to_target
add an extra image metadata item calledformat_string
which contains the string used to pick the write format.Savers can use this extra field (if present) to pick a default save
format. In this case, the magick saver will extract the "bmp".
See: libvips/ruby-vips#319