You've already forked image_processing
mirror of
https://github.com/usetrmnl/image_processing.git
synced 2026-04-29 13:33:11 -07:00
502 lines
18 KiB
Ruby
502 lines
18 KiB
Ruby
require "test_helper"
|
|
require "image_processing/vips"
|
|
require "pathname"
|
|
|
|
describe "ImageProcessing::Vips" do
|
|
before do
|
|
@portrait = fixture_image("portrait.jpg")
|
|
@landscape = fixture_image("landscape.jpg")
|
|
@square = fixture_image("square.jpg")
|
|
end
|
|
|
|
it "applies vips operations" do
|
|
actual = ImageProcessing::Vips.invert.call(@portrait)
|
|
expected = Tempfile.new(["result", ".jpg"], binmode: true).tap do |tempfile|
|
|
image = Vips::Image.new_from_file(@portrait.path)
|
|
image = image.invert
|
|
image.write_to_file(tempfile.path)
|
|
end
|
|
|
|
assert_similar expected, actual
|
|
end
|
|
|
|
it "applies macro operations" do
|
|
actual = ImageProcessing::Vips.resize_to_limit(400, 400).call(@portrait)
|
|
expected = Tempfile.new(["result", ".jpg"], binmode: true).tap do |tempfile|
|
|
image = Vips::Image.new_from_file(@portrait.path)
|
|
image = image.thumbnail_image(400, height: 400, size: :down)
|
|
image.write_to_file(tempfile.path)
|
|
end
|
|
|
|
assert_similar expected, actual
|
|
end
|
|
|
|
it "allows changing metadata" do
|
|
image = ImageProcessing::Vips
|
|
.copy
|
|
.set("icc-profile-data", "foobar")
|
|
.set_type(Vips::BLOB_TYPE, "foo", "bar")
|
|
.remove("exif-data")
|
|
.call(@portrait, save: false)
|
|
|
|
assert_equal "foobar", image.get("icc-profile-data")
|
|
assert_equal "bar", image.get("foo")
|
|
|
|
assert_raises(Vips::Error) { image.get("exif-data") }
|
|
end
|
|
|
|
it "applies format" do
|
|
result = ImageProcessing::Vips.convert("png").call(@portrait)
|
|
assert_equal ".png", File.extname(result.path)
|
|
assert_type "PNG", result
|
|
end
|
|
|
|
it "auto rotates by default" do
|
|
result = ImageProcessing::Vips.call(fixture_image("rotated.jpg"))
|
|
assert_dimensions [600, 800], result
|
|
|
|
result = ImageProcessing::Vips.resize_to_limit(1000, 1000).call(fixture_image("rotated.jpg"))
|
|
assert_dimensions [600, 800], result
|
|
|
|
result = ImageProcessing::Vips.loader(fail: true).resize_to_limit(1000, 1000).call(fixture_image("rotated.jpg"))
|
|
assert_dimensions [600, 800], result
|
|
|
|
result = ImageProcessing::Vips.call(Vips::Image.new_from_file(fixture_image("rotated.jpg").path))
|
|
assert_dimensions [600, 800], result
|
|
|
|
result = ImageProcessing::Vips.loader(autorot: false).call(fixture_image("rotated.jpg"))
|
|
assert_dimensions [800, 600], result
|
|
|
|
result = ImageProcessing::Vips.loader(autorotate: false).call(fixture_image("rotated.jpg"))
|
|
assert_dimensions [800, 600], result
|
|
end
|
|
|
|
it "accepts Vips::Image as source" do
|
|
vips_image = Vips::Image.new_from_file(fixture_image("rotated.jpg").path)
|
|
result = ImageProcessing::Vips.source(vips_image).call
|
|
assert_dimensions [600, 800], result
|
|
end
|
|
|
|
it "applies loader options" do
|
|
result = ImageProcessing::Vips.loader(shrink: 2).call(@portrait)
|
|
assert_dimensions [300, 400], result
|
|
end
|
|
|
|
it "ignores loader options that are not defined" do
|
|
png = ImageProcessing::Vips.convert("png").call(@portrait)
|
|
ImageProcessing::Vips.loader(shrink: 2).call(png)
|
|
end
|
|
|
|
it "raises correct Vips::Error on unknown loader" do
|
|
error = assert_raises(Vips::Error) { ImageProcessing::Vips.convert("jpg").call(Tempfile.new("")) }
|
|
assert_includes error.message, "not a known file format"
|
|
end
|
|
|
|
it "accepts :loader" do
|
|
error = assert_raises(Vips::Error) do
|
|
ImageProcessing::Vips.loader(loader: :tiff).call(@portrait)
|
|
end
|
|
assert_match "Not a TIFF", error.message
|
|
end
|
|
|
|
it "applies saver options" do
|
|
result = ImageProcessing::Vips.saver(strip: true).call(@portrait)
|
|
refute_includes Vips::Image.new_from_file(result.path).get_fields, "exif-data"
|
|
end
|
|
|
|
it "converts :quality saver option to :Q" do
|
|
result1 = ImageProcessing::Vips.saver(quality: 50).call(@portrait)
|
|
result2 = ImageProcessing::Vips.saver(quality: 100).call(@portrait)
|
|
assert result1.size < result2.size
|
|
end
|
|
|
|
it "ignores saver options that are not defined" do
|
|
ImageProcessing::Vips.saver(Q: 85).convert("png").call(@portrait)
|
|
end
|
|
|
|
it "raises correct Vips::Error on unknown saver" do
|
|
error = assert_raises(Vips::Error) { ImageProcessing::Vips.convert("foo").call(@portrait) }
|
|
assert_match /is not a known file format|No known saver for/, error.message
|
|
end
|
|
|
|
it "accepts :saver" do
|
|
result = ImageProcessing::Vips.saver(saver: :png).call(@portrait)
|
|
assert_equal ".jpg", File.extname(result)
|
|
assert_type "PNG", result
|
|
end
|
|
|
|
describe ".valid_image?" do
|
|
it "returns true for correct images" do
|
|
assert ImageProcessing::Vips.valid_image?(@portrait)
|
|
assert ImageProcessing::Vips.valid_image?(copy_to_tempfile(@portrait)) # no extension
|
|
end
|
|
|
|
it "returns false for incorrect images" do
|
|
refute ImageProcessing::Vips.valid_image?(fixture_image("invalid.jpg"))
|
|
refute ImageProcessing::Vips.valid_image?(copy_to_tempfile(fixture_image("invalid.jpg"))) # no extension
|
|
end
|
|
end
|
|
|
|
describe "#resize_to_limit" do
|
|
before do
|
|
@pipeline = ImageProcessing::Vips.source(@portrait)
|
|
end
|
|
|
|
it "srinks image to fit the specified dimensions" do
|
|
assert_dimensions [300, 400], @pipeline.resize_to_limit!(400, 400)
|
|
end
|
|
|
|
it "doesn't enlarge image if it's smaller than specified dimensions" do
|
|
assert_dimensions [600, 800], @pipeline.resize_to_limit!(1000, 1000)
|
|
end
|
|
|
|
it "doesn't require both dimensions" do
|
|
assert_dimensions [300, 400], @pipeline.resize_to_limit!(300, nil)
|
|
assert_dimensions [600, 800], @pipeline.resize_to_limit!(800, nil)
|
|
|
|
assert_dimensions [300, 400], @pipeline.resize_to_limit!(nil, 400)
|
|
assert_dimensions [600, 800], @pipeline.resize_to_limit!(nil, 1000)
|
|
end
|
|
|
|
it "raises exception when neither dimension is specified" do
|
|
assert_raises(ImageProcessing::Error) do
|
|
@pipeline.resize_to_limit!(nil, nil)
|
|
end
|
|
end
|
|
|
|
it "produces correct image" do
|
|
expected = fixture_image("limit.jpg")
|
|
assert_similar expected, @pipeline.resize_to_limit!(400, 400)
|
|
end
|
|
|
|
it "accepts thumbnail options" do
|
|
assert_dimensions [400, 400], @pipeline.resize_to_limit!(400, 400, crop: :centre)
|
|
end
|
|
|
|
it "accepts sharpening options" do
|
|
sharpened = @pipeline.resize_to_limit!(400, 400, sharpen: ImageProcessing::Vips::Processor::SHARPEN_MASK)
|
|
normal = @pipeline.resize_to_limit!(400, 400, sharpen: false)
|
|
assert sharpened.size > normal.size, "Expected sharpened thumbnail to have bigger filesize than not sharpened thumbnail"
|
|
end
|
|
|
|
it "sharpening uses integer precision" do
|
|
sharpened = @pipeline.resize_to_limit(400, 400).call(save: false)
|
|
assert_equal :uchar, sharpened.format
|
|
end
|
|
end
|
|
|
|
describe "#resize_to_fit" do
|
|
before do
|
|
@pipeline = ImageProcessing::Vips.source(@portrait)
|
|
end
|
|
|
|
it "shrinks image to fit specified dimensions" do
|
|
assert_dimensions [300, 400], @pipeline.resize_to_fit!(400, 400)
|
|
end
|
|
|
|
it "enlarges image if it's smaller than given dimensions" do
|
|
assert_dimensions [750, 1000], @pipeline.resize_to_fit!(1000, 1000)
|
|
end
|
|
|
|
it "doesn't require both dimensions" do
|
|
assert_dimensions [300, 400], @pipeline.resize_to_fit!(300, nil)
|
|
assert_dimensions [750, 1000], @pipeline.resize_to_fit!(750, nil)
|
|
|
|
assert_dimensions [300, 400], @pipeline.resize_to_fit!(nil, 400)
|
|
assert_dimensions [750, 1000], @pipeline.resize_to_fit!(nil, 1000)
|
|
end
|
|
|
|
it "raises exception when neither dimension is specified" do
|
|
assert_raises(ImageProcessing::Error) do
|
|
@pipeline.resize_to_limit!(nil, nil)
|
|
end
|
|
end
|
|
|
|
it "produces correct image" do
|
|
expected = fixture_image("fit.jpg")
|
|
assert_similar expected, @pipeline.resize_to_fit!(400, 400)
|
|
end
|
|
|
|
it "accepts thumbnail options" do
|
|
assert_dimensions [400, 400], @pipeline.resize_to_fit!(400, 400, crop: :centre)
|
|
end
|
|
|
|
it "accepts sharpening options" do
|
|
sharpened = @pipeline.resize_to_fit!(400, 400, sharpen: ImageProcessing::Vips::Processor::SHARPEN_MASK)
|
|
normal = @pipeline.resize_to_fit!(400, 400, sharpen: false)
|
|
assert sharpened.size > normal.size, "Expected sharpened thumbnail to have bigger filesize than not sharpened thumbnail"
|
|
end
|
|
|
|
it "sharpening uses integer precision" do
|
|
sharpened = @pipeline.resize_to_limit(400, 400).call(save: false)
|
|
assert_equal :uchar, sharpened.format
|
|
end
|
|
end
|
|
|
|
describe "#resize_to_fill" do
|
|
before do
|
|
@pipeline = ImageProcessing::Vips.source(@portrait)
|
|
end
|
|
|
|
it "resizes and crops the image to fill out the given dimensions" do
|
|
assert_dimensions [400, 400], @pipeline.resize_to_fill!(400, 400)
|
|
end
|
|
|
|
it "enlarges image and crops it if it's smaller than given dimensions" do
|
|
assert_dimensions [1000, 1000], @pipeline.resize_to_fill!(1000, 1000)
|
|
end
|
|
|
|
it "produces correct image" do
|
|
expected = fixture_image("fill.jpg")
|
|
assert_similar expected, @pipeline.resize_to_fill!(400, 400)
|
|
end
|
|
|
|
it "accepts thumbnail options" do
|
|
attention = @pipeline.resize_to_fill!(400, 400, crop: :attention)
|
|
centre = @pipeline.resize_to_fill!(400, 400, crop: :centre)
|
|
refute_similar centre, attention
|
|
end
|
|
|
|
it "accepts sharpening options" do
|
|
sharpened = @pipeline.resize_to_fill!(400, 400, sharpen: ImageProcessing::Vips::Processor::SHARPEN_MASK)
|
|
normal = @pipeline.resize_to_fill!(400, 400, sharpen: false)
|
|
assert sharpened.size > normal.size, "Expected sharpened thumbnail to have bigger filesize than not sharpened thumbnail"
|
|
end
|
|
|
|
it "sharpening uses integer precision" do
|
|
sharpened = @pipeline.resize_to_limit(400, 400).call(save: false)
|
|
assert_equal :uchar, sharpened.format
|
|
end
|
|
end
|
|
|
|
describe "#resize_and_pad" do
|
|
before do
|
|
@pipeline = ImageProcessing::Vips.source(@portrait)
|
|
end
|
|
|
|
it "resizes and fills out the remaining space to fill out the given dimensions" do
|
|
assert_dimensions [400, 400], @pipeline.resize_and_pad!(400, 400)
|
|
end
|
|
|
|
it "enlarges image and fills out the remaining space to fill out the given dimensions" do
|
|
assert_dimensions [1000, 1000], @pipeline.resize_and_pad!(1000, 1000)
|
|
end
|
|
|
|
it "produces correct image when shrinking" do
|
|
result = @pipeline.convert("png").resize_and_pad!(400, 400, alpha: true)
|
|
assert_similar fixture_image("pad.png"), result
|
|
assert_equal 4, Vips::Image.new_from_file(result.path).bands
|
|
|
|
transparent_png = @pipeline.add_alpha.convert!("png")
|
|
result = @pipeline.source(transparent_png).resize_and_pad!(400, 400, alpha: true)
|
|
assert_similar fixture_image("pad.png"), result
|
|
assert_equal 4, Vips::Image.new_from_file(result.path).bands
|
|
end
|
|
|
|
it "produces correct image when enlarging" do
|
|
@pipeline = ImageProcessing::Vips.source(@landscape)
|
|
expected = fixture_image("pad-large.jpg")
|
|
assert_similar expected, @pipeline.resize_and_pad!(1000, 1000, background: [55, 126, 34])
|
|
end
|
|
|
|
it "accepts gravity" do
|
|
centre = @pipeline.resize_and_pad!(400, 400)
|
|
northwest = @pipeline.resize_and_pad!(400, 400, gravity: "north-west")
|
|
refute_similar centre, northwest
|
|
end
|
|
|
|
it "accepts thumbnail options" do
|
|
pad = @pipeline.resize_and_pad!(400, 400)
|
|
crop = @pipeline.resize_and_pad!(400, 400, crop: :entropy)
|
|
refute_similar pad, crop
|
|
end
|
|
|
|
it "accepts sharpening options" do
|
|
sharpened = @pipeline.resize_and_pad!(400, 400, sharpen: ImageProcessing::Vips::Processor::SHARPEN_MASK)
|
|
normal = @pipeline.resize_and_pad!(400, 400, sharpen: false)
|
|
assert sharpened.size > normal.size, "Expected sharpened thumbnail to have bigger filesize than not sharpened thumbnail"
|
|
end
|
|
|
|
it "sharpening uses integer precision" do
|
|
sharpened = @pipeline.resize_to_limit(400, 400).call(save: false)
|
|
assert_equal :uchar, sharpened.format
|
|
end
|
|
end
|
|
|
|
describe "#resize_to_cover" do
|
|
before do
|
|
@portrait_pipeline = ImageProcessing::Vips.source(@portrait)
|
|
@landscape_pipeline = ImageProcessing::Vips.source(@landscape)
|
|
@square_pipeline = ImageProcessing::Vips.source(@square)
|
|
end
|
|
|
|
it "resizes the portrait image to fill out the given landscape dimensions" do
|
|
assert_dimensions [300, 400], @portrait_pipeline.resize_to_cover!(300, 200)
|
|
end
|
|
|
|
it "resizes the portrait image to fill out the given portrait dimensions" do
|
|
assert_dimensions [225, 300], @portrait_pipeline.resize_to_cover!(200, 300)
|
|
end
|
|
|
|
it "resizes the portrait image to fill out the given square dimensions" do
|
|
assert_dimensions [300, 400], @portrait_pipeline.resize_to_cover!(300, 300)
|
|
end
|
|
|
|
it "resizes the landscape image to fill out the given portrait dimensions" do
|
|
assert_dimensions [400, 300], @landscape_pipeline.resize_to_cover!(200, 300)
|
|
end
|
|
|
|
it "resizes the landscape image to fill out the given landscape dimensions" do
|
|
assert_dimensions [300, 225], @landscape_pipeline.resize_to_cover!(300, 200)
|
|
end
|
|
|
|
it "resizes the landscape image to fill out the given square dimensions" do
|
|
assert_dimensions [400, 300], @landscape_pipeline.resize_to_cover!(300, 300)
|
|
end
|
|
|
|
it "resizes the square image to fill out the given portrait dimensions" do
|
|
assert_dimensions [300, 300], @square_pipeline.resize_to_cover!(200, 300)
|
|
end
|
|
|
|
it "resizes the square image to fill out the given landscape dimensions" do
|
|
assert_dimensions [300, 300], @square_pipeline.resize_to_cover!(300, 200)
|
|
end
|
|
|
|
it "resizes the square image to fill out the given square dimensions" do
|
|
assert_dimensions [300, 300], @square_pipeline.resize_to_cover!(300, 300)
|
|
end
|
|
|
|
it "produces correct image" do
|
|
expected = fixture_image("cover.jpg")
|
|
assert_similar expected, @portrait_pipeline.resize_to_cover!(300, 200)
|
|
end
|
|
|
|
it "accepts thumbnail options except :crop" do
|
|
attention = @portrait_pipeline.resize_to_cover!(400, 400, crop: :attention)
|
|
centre = @portrait_pipeline.resize_to_cover!(400, 400, crop: :centre)
|
|
assert_similar centre, attention
|
|
end
|
|
|
|
it "accepts sharpening options" do
|
|
sharpened = @portrait_pipeline.resize_to_cover!(400, 400, sharpen: ImageProcessing::Vips::Processor::SHARPEN_MASK)
|
|
normal = @portrait_pipeline.resize_to_cover!(400, 400, sharpen: false)
|
|
assert sharpened.size > normal.size, "Expected sharpened thumbnail to have bigger filesize than not sharpened thumbnail"
|
|
end
|
|
|
|
it "sharpening uses integer precision" do
|
|
sharpened = @portrait_pipeline.resize_to_cover(400, 400).call(save: false)
|
|
assert_equal :uchar, sharpened.format
|
|
end
|
|
end
|
|
|
|
describe "#rotate" do
|
|
before do
|
|
@pipeline = ImageProcessing::Vips.source(@portrait)
|
|
end
|
|
|
|
it "rotates by multiples of 90" do
|
|
assert_dimensions [600, 800], @pipeline.rotate!(0)
|
|
assert_dimensions [800, 600], @pipeline.rotate!(90)
|
|
assert_dimensions [600, 800], @pipeline.rotate!(180)
|
|
assert_dimensions [800, 600], @pipeline.rotate!(270)
|
|
end
|
|
|
|
it "works for angles outside of 0-360 degrees" do
|
|
assert_dimensions [600, 800], @pipeline.rotate!(360)
|
|
assert_dimensions [800, 600], @pipeline.rotate!(450)
|
|
assert_dimensions [800, 600], @pipeline.rotate!(-90)
|
|
end
|
|
|
|
it "rotates by arbitrary angle" do
|
|
assert_dimensions [990, 990], @pipeline.rotate!(45)
|
|
end
|
|
|
|
it "accepts background color" do
|
|
assert_dimensions [990, 990], @pipeline.rotate!(45, background: [0, 0, 0])
|
|
end
|
|
end
|
|
|
|
describe "#composite" do
|
|
before do
|
|
@pipeline = ImageProcessing::Vips.source(@portrait)
|
|
end
|
|
|
|
it "accepts String, Pathname, and File object" do
|
|
assert_similar fixture_image("composited.jpg"), @pipeline.composite!(@landscape)
|
|
assert_similar fixture_image("composited.jpg"), @pipeline.composite!(@landscape.path)
|
|
assert_similar fixture_image("composited.jpg"), @pipeline.composite!(Pathname(@landscape.path))
|
|
end
|
|
|
|
it "accepts Vips::Image object" do
|
|
overlay = Vips::Image.new_from_file(@landscape.path)
|
|
assert_similar fixture_image("composited.jpg"), @pipeline.composite!(overlay)
|
|
end
|
|
|
|
it "accepts :mode" do
|
|
refute_similar fixture_image("composited.jpg"), @pipeline.composite!(@landscape, mode: "clear")
|
|
end
|
|
|
|
it "accepts :gravity" do
|
|
result = @pipeline.composite!(@landscape, gravity: "centre")
|
|
refute_similar fixture_image("composited.jpg"), result
|
|
assert_dimensions [600, 800], result
|
|
|
|
assert_raises(Vips::Error) { @pipeline.composite!(@landscape, gravity: "foo") }
|
|
end
|
|
|
|
it "accepts :offset" do
|
|
result = @pipeline.composite!(@landscape, offset: [50, -50])
|
|
refute_similar fixture_image("composited.jpg"), result
|
|
assert_dimensions [600, 800], result
|
|
end
|
|
|
|
it "accepts additional options" do
|
|
@pipeline.composite!(@landscape, compositing_space: :grey16)
|
|
|
|
assert_raises(Vips::Error) { @pipeline.composite!(@landscape, compositing_space: :foo) }
|
|
end
|
|
|
|
it "accepts Vips::Image#composite parameters" do
|
|
overlay = Vips::Image.new_from_file(@landscape.path)
|
|
original = @pipeline.custom! { |image| image.composite(overlay, :over) }
|
|
|
|
assert_similar original, @pipeline.composite!(overlay, :over)
|
|
assert_similar original, @pipeline.composite!(@landscape, :over)
|
|
assert_similar original, @pipeline.composite!(@landscape.path, :over)
|
|
assert_similar original, @pipeline.composite!(Pathname(@landscape.path), :over)
|
|
|
|
assert_similar original, @pipeline.composite!([overlay], :over)
|
|
assert_similar original, @pipeline.composite!([@landscape], :over)
|
|
assert_similar original, @pipeline.composite!([@landscape.path], :over)
|
|
assert_similar original, @pipeline.composite!([Pathname(@landscape.path)], :over)
|
|
end
|
|
end
|
|
|
|
describe "resize-on-load" do
|
|
it "retains order of operations" do
|
|
result = ImageProcessing::Vips.source(@portrait)
|
|
.resize_to_fit(400, 400)
|
|
.crop(0, 0, 300, 300)
|
|
.call
|
|
|
|
assert_dimensions [300, 300], result
|
|
|
|
result = ImageProcessing::Vips.source(@portrait)
|
|
.crop(0, 0, 300, 300)
|
|
.resize_to_fit(400, 400)
|
|
.call
|
|
|
|
assert_dimensions [400, 400], result
|
|
end
|
|
|
|
it "is skipped on loader options" do
|
|
pipeline = ImageProcessing::Vips.source(fixture_image("invalid.jpg"))
|
|
.loader(fail: true)
|
|
.resize_to_fit(400, 400)
|
|
|
|
error = assert_raises(Vips::Error) { pipeline.call }
|
|
assert_match "Corrupt JPEG data", error.message
|
|
end
|
|
end
|
|
end
|