├── spec
├── images
│ ├── uhdr.jpg
│ ├── watermark.png
│ └── Gugg_coloured.jpg
├── helpers.lua
├── cache_spec.lua
├── interpolate_spec.lua
├── enum_spec.lua
├── connection_spec.lua
├── gvalue_spec.lua
├── meta_spec.lua
├── convenience_spec.lua
├── write_spec.lua
├── overloads_spec.lua
└── new_spec.lua
├── .gitignore
├── example
├── images
│ ├── Gugg_coloured.jpg
│ └── PNG_transparency_demonstration_1.png
├── hello-world.lua
├── target.lua
├── buffer.lua
├── combine.lua
├── soak.lua
├── array.lua
├── watermark.lua
└── noise.lua
├── .luacheckrc
├── .busted
├── src
├── vips
│ ├── Image.lua
│ ├── verror.lua
│ ├── version.lua
│ ├── Interpolate.lua
│ ├── Source.lua
│ ├── Target.lua
│ ├── Connection.lua
│ ├── log.lua
│ ├── vobject.lua
│ ├── voperation.lua
│ ├── gvalue.lua
│ ├── cdefs.lua
│ └── Image_methods.lua
└── vips.lua
├── LICENSE
├── lua-vips-1.1-12.rockspec
├── CHANGELOG.md
├── .github
└── workflows
│ └── ci.yml
└── README.md
/spec/images/uhdr.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/libvips/lua-vips/HEAD/spec/images/uhdr.jpg
--------------------------------------------------------------------------------
/spec/images/watermark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/libvips/lua-vips/HEAD/spec/images/watermark.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .*.swp
2 | lua-vips-*.src.rock
3 |
4 | # LuaCov
5 | luacov.stats.out
6 | luacov.report.out
7 |
--------------------------------------------------------------------------------
/spec/images/Gugg_coloured.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/libvips/lua-vips/HEAD/spec/images/Gugg_coloured.jpg
--------------------------------------------------------------------------------
/example/images/Gugg_coloured.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/libvips/lua-vips/HEAD/example/images/Gugg_coloured.jpg
--------------------------------------------------------------------------------
/.luacheckrc:
--------------------------------------------------------------------------------
1 | files["spec/**/*.lua"] = {
2 | std = "+busted",
3 | }
4 | exclude_files = { ".install", ".luarocks", ".lua" }
5 |
--------------------------------------------------------------------------------
/.busted:
--------------------------------------------------------------------------------
1 | return {
2 | default = {
3 | helper = "./spec/helpers.lua",
4 | coverage = os.getenv("TEST_COVERAGE") == "1"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/example/images/PNG_transparency_demonstration_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/libvips/lua-vips/HEAD/example/images/PNG_transparency_demonstration_1.png
--------------------------------------------------------------------------------
/src/vips/Image.lua:
--------------------------------------------------------------------------------
1 | -- the definition of the image class
2 | --
3 | -- we have to split the definition of Image from the methods to avoid
4 | -- recursive requires between the methods and voperation
5 |
6 | local Image = {
7 | mt = {}
8 | }
9 |
10 | return Image
11 |
--------------------------------------------------------------------------------
/example/hello-world.lua:
--------------------------------------------------------------------------------
1 | local vips = require "vips"
2 |
3 | -- uncomment for very chatty output
4 | -- vips.log.enable(true)
5 |
6 | local image1 = vips.Image.text("Hello World!", { dpi = 300 })
7 | print("writing to x.png ...")
8 | image1:write_to_file("x.png")
9 |
10 |
--------------------------------------------------------------------------------
/src/vips/verror.lua:
--------------------------------------------------------------------------------
1 | -- handle the libvips error buffer
2 |
3 | local ffi = require "ffi"
4 |
5 | local vips_lib = ffi.load(ffi.os == "Windows" and "libvips-42.dll" or "vips")
6 |
7 | local verror = {
8 | -- get and clear the error buffer
9 | get = function()
10 | local errstr = ffi.string(vips_lib.vips_error_buffer())
11 | vips_lib.vips_error_clear()
12 |
13 | return errstr
14 | end
15 | }
16 |
17 | return verror
18 |
--------------------------------------------------------------------------------
/example/target.lua:
--------------------------------------------------------------------------------
1 | local vips = require "vips"
2 |
3 | if #arg ~= 2 then
4 | error("Usage: lua target.lua ~/pics/k2.png .avif > x")
5 | end
6 |
7 | local infilename = arg[1]
8 | local fmt = arg[2]
9 |
10 | local descriptor = {
11 | stdin = 0,
12 | stdout = 1,
13 | stderr = 2,
14 | }
15 |
16 | local image = vips.Image.new_from_file(infilename)
17 | local target = vips.Target.new_to_descriptor(descriptor.stdout)
18 | image:write_to_target(target, fmt)
19 |
--------------------------------------------------------------------------------
/src/vips/version.lua:
--------------------------------------------------------------------------------
1 | -- detect and test libvips version
2 |
3 | local ffi = require "ffi"
4 |
5 | local vips_lib = ffi.load(ffi.os == "Windows" and "libvips-42.dll" or "vips")
6 |
7 | local version = {}
8 |
9 | version = {
10 | major = vips_lib.vips_version(0),
11 | minor = vips_lib.vips_version(1),
12 | micro = vips_lib.vips_version(2),
13 |
14 | -- test for libvips version is better than x.y .. we use this to turn on
15 | -- various workarounds for older libvips
16 | at_least = function(x, y)
17 | return version.major > x or (version.major == x and version.minor >= y)
18 | end
19 | }
20 |
21 | return version
22 |
--------------------------------------------------------------------------------
/example/buffer.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/luajit
2 |
3 | -- load and save images to and from memory buffers
4 |
5 | local vips = require "vips"
6 |
7 | if #arg ~= 1 then
8 | print("usage: luajit buffer.lua image-file")
9 | error()
10 | end
11 | local f = assert(io.open(arg[1], "rb"))
12 | local content = f:read("*all")
13 |
14 | local im = vips.Image.new_from_buffer(content, "", {access = "sequential"})
15 |
16 | -- brighten 20%
17 | im = (im * 1.2):cast("uchar")
18 |
19 | -- print as mime jpg
20 | local buffer = im:write_to_buffer(".jpg", {Q = 90})
21 | print("Content-length: " .. #buffer)
22 | print("Content-type: image/jpeg")
23 | print("")
24 | print(buffer)
25 |
--------------------------------------------------------------------------------
/example/combine.lua:
--------------------------------------------------------------------------------
1 | local vips = require "vips"
2 |
3 | -- uncomment for very chatty output
4 | -- vips.log.enable(true)
5 |
6 | local main_filename = "images/Gugg_coloured.jpg"
7 | local watermark_filename = "images/PNG_transparency_demonstration_1.png"
8 |
9 | local main = vips.Image.new_from_file(main_filename)
10 | local watermark = vips.Image.new_from_file(watermark_filename)
11 |
12 | -- scale the alpha down to 30% transparency
13 | watermark = watermark * { 1, 1, 1, 0.3 }
14 |
15 | -- composite onto the base image at the top left
16 | local result = main:composite(watermark, "over", { x = 10, y = 10 })
17 |
18 | print("writing x.jpg ...")
19 | result:write_to_file("x.jpg")
20 |
21 |
--------------------------------------------------------------------------------
/example/soak.lua:
--------------------------------------------------------------------------------
1 | -- a lua version of
2 | -- https://github.com/libvips/pyvips/blob/master/examples/soak-test.py
3 | -- this should run in a steady amount of memory
4 |
5 | local vips = require "vips"
6 |
7 | vips.leak_set(true)
8 | vips.cache_set_max(0)
9 |
10 | if #arg ~= 2 then
11 | print("usage: luajit soak.lua image-file iterations")
12 | error()
13 | end
14 |
15 | local im
16 |
17 | for i = 0, tonumber(arg[2]) do
18 | print("loop ", i)
19 |
20 | im = vips.Image.new_from_file(arg[1])
21 | im = im:embed(100, 100, 3000, 3000, { extend = "mirror" })
22 | -- local buf = im:write_to_buffer(".jpg")
23 | -- im:write_to_file("x.jpg")
24 | im:write_to_file("x.v")
25 | im = nil -- luacheck: ignore
26 |
27 | collectgarbage()
28 | end
29 |
--------------------------------------------------------------------------------
/spec/helpers.lua:
--------------------------------------------------------------------------------
1 | -- Pre-load the vips module
2 | require "vips"
3 |
4 | local assert = require "luassert.assert"
5 | local say = require "say"
6 |
7 | local function almost_equal(_, arguments)
8 | local threshold = arguments[3] or 0.001
9 |
10 | if type(arguments[1]) ~= "number" or type(arguments[2]) ~= "number" then
11 | return false
12 | end
13 |
14 | return math.abs(arguments[1] - arguments[2]) < threshold
15 | end
16 |
17 | say:set("assertion.almost_equal.positive",
18 | "Expected %s to almost equal %s")
19 | say:set("assertion.almost_equal.negative",
20 | "Expected %s to not almost equal %s")
21 | assert:register("assertion", "almost_equal", almost_equal,
22 | "assertion.almost_equal.positive",
23 | "assertion.almost_equal.negative")
--------------------------------------------------------------------------------
/src/vips/Interpolate.lua:
--------------------------------------------------------------------------------
1 | -- make image interpolators, see affine
2 |
3 | local ffi = require "ffi"
4 |
5 | local verror = require "vips.verror"
6 | local vobject = require "vips.vobject"
7 |
8 | local vips_lib = ffi.load(ffi.os == "Windows" and "libvips-42.dll" or "vips")
9 |
10 | local Interpolate = {}
11 |
12 | Interpolate.vobject = function(self)
13 | return ffi.cast(vobject.typeof, self)
14 | end
15 |
16 | Interpolate.new = function(name)
17 | -- there could potentially be other params here, but ignore that for now
18 | local interpolate = vips_lib.vips_interpolate_new(name)
19 | if interpolate == nil then
20 | error("no such interpolator\n" .. verror.get())
21 | end
22 |
23 | return vobject.new(interpolate)
24 | end
25 |
26 | return ffi.metatype("VipsInterpolate", {
27 | __index = Interpolate
28 | })
29 |
30 |
31 |
--------------------------------------------------------------------------------
/spec/cache_spec.lua:
--------------------------------------------------------------------------------
1 | local vips = require "vips"
2 |
3 | -- test cache control
4 | describe("cache control", function()
5 |
6 | setup(function()
7 | -- vips.log.enable(true)
8 | end)
9 |
10 | it("can set number of operations to cache", function()
11 | local max = vips.get_max()
12 |
13 | vips.set_max(10)
14 | assert.are.equal(vips.get_max(), 10)
15 | vips.set_max(max)
16 | end)
17 |
18 | it("can limit the number of operations to cache by open files", function()
19 | local max = vips.get_max_files()
20 |
21 | vips.set_max_files(10)
22 | assert.are.equal(vips.get_max_files(), 10)
23 | vips.set_max_files(max)
24 | end)
25 |
26 | it("can limit the number of operations to cache by memory", function()
27 | local max = vips.get_max_mem()
28 |
29 | vips.set_max_mem(10)
30 | assert.are.equal(vips.get_max_mem(), 10)
31 | vips.set_max_mem(max)
32 | end)
33 | end)
34 |
--------------------------------------------------------------------------------
/example/array.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/luajit
2 |
3 | -- turn a vips image into a lua array
4 |
5 | local vips = require "vips"
6 | local ffi = require "ffi"
7 |
8 | -- make a tiny two band u16 image whose pixels are their coordinates
9 | local im = vips.Image.xyz(3, 2):cast("ushort")
10 |
11 | -- write as a C-style memory array, so band-interleaved, a series of scanlines
12 | --
13 | -- "data" is a pointer of type uchar*, though the underlying memory is really
14 | -- pairs of int16s, see above
15 | local data = im:write_to_memory()
16 |
17 | -- the type of each pixel ... a pair of shorts
18 | ffi.cdef [[
19 | typedef struct {
20 | unsigned short x;
21 | unsigned short y;
22 | } pixel;
23 | ]]
24 | -- and cast the image pointer to a 1D array of pixel structs
25 | local ptype = ffi.typeof("pixel*")
26 | local array = ffi.cast(ptype, data)
27 |
28 | -- and print! ffi arrays number from 0
29 | for y = 0, im:height() - 1 do
30 | for x = 0, im:width() - 1 do
31 | local i = x + y * im:width()
32 |
33 | print("x = ", x, "y = ", y, "value = ", array[i].x, array[i].y)
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/spec/interpolate_spec.lua:
--------------------------------------------------------------------------------
1 | local vips = require "vips"
2 |
3 | -- test image interpolation
4 | describe("image interpolation", function()
5 | setup(function()
6 | -- vips.log.enable(true)
7 | end)
8 |
9 | it("can rotate an image using nearest interpolator", function()
10 | local interpolate = vips.Interpolate.new("nearest")
11 | local original = {
12 | { 1, 2, 3 },
13 | { 4, 5, 6 },
14 | { 7, 8, 9 },
15 | }
16 | local rotated = {
17 | { 0.0, 0.0, 1.0, 0.0 },
18 | { 0.0, 0.0, 1.0, 2.0 },
19 | { 0.0, 7.0, 5.0, 3.0 },
20 | { 0.0, 8.0, 9.0, 6.0 }
21 | }
22 | local im = vips.Image.new_from_array(original)
23 | local rot = im:rotate(45, { interpolate = interpolate })
24 | assert.are.equal(rot:width(), 4)
25 | assert.are.equal(rot:height(), 4)
26 | assert.are.equal(rot:bands(), 1)
27 | for x = 1, 4 do
28 | for y = 1, 4 do
29 | assert.are_equal(rot(x - 1, y - 1), rotated[y][x])
30 | end
31 | end
32 | end)
33 | end)
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 John Cupitt
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/example/watermark.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/luajit
2 |
3 | -- add a simple text watermark to an image
4 | -- ./watermark.lua ~/pics/IMG_0073.JPG x.jpg "Hello world!"
5 |
6 | local vips = require "vips"
7 |
8 | -- uncomment for very chatty output
9 | -- vips.log.enable(true)
10 |
11 | if #arg ~= 3 then
12 | print("usage: luajit watermark.lua input-image-file output-image-file text")
13 | error()
14 | end
15 | local im = vips.Image.new_from_file(arg[1], {access = "sequential"})
16 |
17 | -- make the text mask
18 | local text = vips.Image.text(arg[3],
19 | {width = 200, dpi = 200, align = "centre", font = "sans bold"})
20 | text = text:rotate(-45)
21 | -- make the text transparent
22 | text = (text * 0.3):cast("uchar")
23 | text = text:gravity("centre", 200, 200)
24 | -- this block of pixels will be reused many times ... make a copy
25 | text = text:copy_memory()
26 | text = text:replicate(1 + math.floor(im:width() / text:width()),
27 | 1 + math.floor(im:height() / text:height()))
28 | text = text:crop(0, 0, im:width(), im:height())
29 |
30 | -- we make a constant colour image and attach the text mask as the alpha
31 | local overlay =
32 | text:new_from_image({255, 128, 128}):copy{interpretation = "srgb"}
33 | overlay = overlay:bandjoin(text)
34 |
35 | -- overlay the text
36 | im = im:composite(overlay, "over")
37 |
38 | im:write_to_file(arg[2])
39 |
--------------------------------------------------------------------------------
/src/vips/Source.lua:
--------------------------------------------------------------------------------
1 | -- An output connection
2 |
3 | local ffi = require "ffi"
4 |
5 | local verror = require "vips.verror"
6 | local Connection = require "vips.Connection"
7 |
8 | local vips_lib = ffi.load(ffi.os == "Windows" and "libvips-42.dll" or "vips")
9 |
10 | local Source = {}
11 |
12 | Source.new_from_descriptor = function(descriptor)
13 | local source = vips_lib.vips_source_new_from_descriptor(descriptor)
14 | if source == ffi.NULL then
15 | error("Can't create source from descriptor " .. descriptor .. "\n" .. verror.get())
16 | end
17 |
18 | return Connection.new(source)
19 | end
20 |
21 | Source.new_from_file = function(filename)
22 | local source = vips_lib.vips_source_new_from_file(filename)
23 | if source == ffi.NULL then
24 | error("Can't create source from filename " .. filename .. "\n" .. verror.get())
25 | end
26 |
27 | return Connection.new(source)
28 | end
29 |
30 | Source.new_from_memory = function(data) -- data is an FFI memory array containing the image data
31 | local source = vips_lib.vips_source_new_from_memory(data, ffi.sizeof(data))
32 | if source == ffi.NULL then
33 | error("Can't create input source from memory \n" .. verror.get())
34 | end
35 |
36 | return Connection.new(source)
37 | end
38 |
39 | return ffi.metatype("VipsSource", {
40 | __index = Source
41 | })
42 |
--------------------------------------------------------------------------------
/src/vips/Target.lua:
--------------------------------------------------------------------------------
1 | -- An input connection
2 |
3 | local ffi = require "ffi"
4 |
5 | local Connection = require "vips.Connection"
6 |
7 | local vips_lib = ffi.load(ffi.os == "Windows" and "libvips-42.dll" or "vips")
8 |
9 | local Target = {}
10 |
11 | Target.new_to_descriptor = function(descriptor)
12 | collectgarbage("stop")
13 | local target = vips_lib.vips_target_new_to_descriptor(descriptor)
14 | collectgarbage("restart")
15 | if target == ffi.NULL then
16 | error("can't create output target from descriptor " .. descriptor)
17 | else
18 | return Connection.new(target)
19 | end
20 | end
21 |
22 | Target.new_to_file = function(filename)
23 | collectgarbage("stop")
24 | local target = vips_lib.vips_target_new_to_file(filename)
25 | collectgarbage("restart")
26 | if target == ffi.NULL then
27 | error("can't create output target from filename " .. filename)
28 | else
29 | return Connection.new(target)
30 | end
31 | end
32 |
33 | Target.new_to_memory = function()
34 | collectgarbage("stop")
35 | local target = vips_lib.vips_target_new_to_memory()
36 | collectgarbage("restart")
37 | if target == ffi.NULL then
38 | error("can't create output target from memory")
39 | else
40 | return Connection.new(target)
41 | end
42 | end
43 |
44 | return ffi.metatype("VipsTarget", {
45 | __index = Target
46 | })
47 |
--------------------------------------------------------------------------------
/example/noise.lua:
--------------------------------------------------------------------------------
1 | local vips = require "vips"
2 |
3 | local size = 1024
4 |
5 | -- perlin's "turbulence" image
6 | local function turbulence(turb_size)
7 | local image
8 | local iterations = math.log(turb_size, 2) - 2
9 | for i = 0, iterations do
10 | -- make perlin noise at this scale
11 | local layer = vips.Image.perlin(turb_size, turb_size, {
12 | cell_size = turb_size / math.pow(2, i)
13 | })
14 | layer = layer:abs() * (1.0 / (i + 1))
15 |
16 | -- and sum
17 | if image then
18 | image = image + layer
19 | else
20 | image = layer
21 | end
22 | end
23 |
24 | return image
25 | end
26 |
27 | -- make a gradient colour map ... a smooth fade from start to stop, with
28 | -- start and stop as CIELAB colours, then map as sRGB
29 | local function gradient(start, stop)
30 | local lut = vips.Image.identity() / 255
31 | lut = lut * start + (lut * -1 + 1) * stop
32 | return lut:colourspace("srgb", { source_space = "lab" })
33 | end
34 |
35 | -- make a turbulent stripe pattern
36 | local stripe = vips.Image.xyz(size, size):extract_band(0)
37 | stripe = (stripe * 360 * 4 / size + turbulence(size) * 700):sin()
38 |
39 | -- make a colour map ... we want a smooth gradient from white to dark brown
40 | -- colours here in CIELAB
41 | local dark_brown = { 7.45, 4.3, 8 }
42 | local white = { 100, 0, 0 }
43 | local lut = gradient(dark_brown, white)
44 |
45 | -- rescale to 0 - 255 and colour with our lut
46 | stripe = ((stripe + 1) * 128):maplut(lut)
47 |
48 | print("writing x.png ...")
49 | stripe:write_to_file("x.png")
50 |
--------------------------------------------------------------------------------
/spec/enum_spec.lua:
--------------------------------------------------------------------------------
1 | local vips = require "vips"
2 |
3 | -- test metadata read/write
4 | describe("enum expansions", function()
5 | local array, im
6 |
7 | setup(function()
8 | array = { 1, 2, 3, 4 }
9 | im = vips.Image.new_from_array(array)
10 | -- vips.log.enable(true)
11 | end)
12 |
13 | -- there are loads of expansions, just test one of each type
14 |
15 | it("can call pow() with a constant arg", function()
16 | local im2 = im:pow(2)
17 |
18 | assert.are.equal(im2:width(), 4)
19 | assert.are.equal(im2:height(), 1)
20 | assert.are.equal(im2:bands(), 1)
21 | assert.are.equal(im2:avg(), (1 + 4 + 9 + 16) / 4)
22 | end)
23 |
24 | it("can call pow() with an image arg", function()
25 | local im2 = im:pow(im)
26 |
27 | assert.are.equal(im2:width(), 4)
28 | assert.are.equal(im2:height(), 1)
29 | assert.are.equal(im2:bands(), 1)
30 | assert.are.equal(im2:avg(), (1 ^ 1 + 2 ^ 2 + 3 ^ 3 + 4 ^ 4) / 4)
31 | end)
32 |
33 | it("can call lshift()", function()
34 | local im2 = im:lshift(1)
35 |
36 | assert.are.equal(im2:width(), 4)
37 | assert.are.equal(im2:height(), 1)
38 | assert.are.equal(im2:bands(), 1)
39 | assert.are.equal(im2:avg(), (2 + 4 + 6 + 8) / 4)
40 | end)
41 |
42 | it("can call less()", function()
43 | local im2 = im:less(2)
44 |
45 | assert.are.equal(im2:width(), 4)
46 | assert.are.equal(im2:height(), 1)
47 | assert.are.equal(im2:bands(), 1)
48 | assert.are.equal(im2:avg(), (255 + 0 + 0 + 0) / 4)
49 | end)
50 | end)
51 |
--------------------------------------------------------------------------------
/src/vips/Connection.lua:
--------------------------------------------------------------------------------
1 | -- abstract base Connection class
2 |
3 | local ffi = require "ffi"
4 |
5 | local vobject = require "vips.vobject"
6 |
7 | local vips_lib = ffi.load(ffi.os == "Windows" and "libvips-42.dll" or "vips")
8 |
9 | local Connection_method = {}
10 |
11 | local Connection = {
12 | mt = {
13 | __index = Connection_method,
14 | }
15 | }
16 |
17 | function Connection.mt:__tostring()
18 | return self:filename() or self:nick() or "(nil)"
19 | end
20 |
21 | Connection.new = function(vconnection)
22 | local connection = {}
23 | connection.vconnection = vobject.new(vconnection)
24 | return setmetatable(connection, Connection.mt)
25 | end
26 | function Connection_method:vobject()
27 | return ffi.cast(vobject.typeof, self.vconnection)
28 | end
29 |
30 | function Connection_method:filename()
31 | -- Get the filename asscoiated with a connection. Return nil if there is no associated file.
32 | local so = ffi.cast('VipsConnection *', self.vconnection)
33 | local filename = vips_lib.vips_connection_filename(so)
34 | if filename == ffi.NULL then
35 | return nil
36 | else
37 | return ffi.string(filename)
38 | end
39 | end
40 |
41 | function Connection_method:nick()
42 | -- Make a human-readable name for a connection suitable for error messages.
43 |
44 | local so = ffi.cast('VipsConnection *', self.vconnection)
45 | local nick = vips_lib.vips_connection_nick(so)
46 | if nick == ffi.NULL then
47 | return nil
48 | else
49 | return ffi.string(nick)
50 | end
51 | end
52 |
53 | return ffi.metatype("VipsConnection", {
54 | __index = Connection
55 | })
56 |
--------------------------------------------------------------------------------
/lua-vips-1.1-12.rockspec:
--------------------------------------------------------------------------------
1 | package = "lua-vips"
2 | version = "1.1-12"
3 | rockspec_format = "3.0"
4 |
5 | source = {
6 | url = "git://github.com/libvips/lua-vips.git",
7 | tag = "v1.1-12",
8 | }
9 |
10 | description = {
11 | summary = "A fast image processing library with low memory needs.",
12 | detailed = [[
13 | This rock implements a binding for the libvips image processing library.
14 | It is usually faster and needs less memory than similar libraries.
15 |
16 | For use with standard Lua, the dependency luaffi-tkl is used as a drop-in
17 | replacement for LuaJIT's ffi module.
18 | ]],
19 | homepage = "https://github.com/libvips/lua-vips",
20 | license = "MIT",
21 | labels = { "image" }
22 | }
23 |
24 | dependencies = {
25 | "lua >= 5.1, < 5.5", -- standard Lua or LuaJIT >= 2.0
26 | "luaffi-tkl >= 1.0" -- provided by VM with LuaJIT, use `luarocks config rocks_provided.luaffi-tkl 2.1-1` in that case
27 | }
28 |
29 | test_dependencies = {
30 | "busted"
31 | }
32 |
33 | test = {
34 | type = "busted"
35 | }
36 |
37 | build = {
38 | type = "builtin",
39 | modules = {
40 | vips = "src/vips.lua",
41 | ["vips.cdefs"] = "src/vips/cdefs.lua",
42 | ["vips.verror"] = "src/vips/verror.lua",
43 | ["vips.version"] = "src/vips/version.lua",
44 | ["vips.log"] = "src/vips/log.lua",
45 | ["vips.gvalue"] = "src/vips/gvalue.lua",
46 | ["vips.vobject"] = "src/vips/vobject.lua",
47 | ["vips.voperation"] = "src/vips/voperation.lua",
48 | ["vips.Image"] = "src/vips/Image.lua",
49 | ["vips.Image_methods"] = "src/vips/Image_methods.lua",
50 | ["vips.Interpolate"] = "src/vips/Interpolate.lua",
51 | ["vips.Connection"] = "src/vips/Connection.lua",
52 | ["vips.Source"] = "src/vips/Source.lua",
53 | ["vips.Target"] = "src/vips/Target.lua",
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/spec/connection_spec.lua:
--------------------------------------------------------------------------------
1 | local vips = require "vips"
2 | local ffi = require "ffi"
3 |
4 | local JPEG_FILE = "./spec/images/Gugg_coloured.jpg"
5 | local TMP_FILE = ffi.os == "Windows" and os.getenv("TMP") .. "\\x.png" or "/tmp/x.png"
6 |
7 | describe("test connection", function()
8 | setup(function()
9 | -- vips.log.enable(true)
10 | end)
11 |
12 | describe("to file target", function()
13 | local target
14 |
15 | setup(function()
16 | target = vips.Target.new_to_file(TMP_FILE)
17 | end)
18 |
19 | it("can create image from file source", function()
20 | local source = vips.Source.new_from_file(JPEG_FILE)
21 | local image = vips.Image.new_from_source(source, '', { access = 'sequential' })
22 | image:write_to_target(target, '.png')
23 |
24 | local image1 = vips.Image.new_from_file(JPEG_FILE, { access = 'sequential' })
25 | local image2 = vips.Image.new_from_file(TMP_FILE, { access = 'sequential' })
26 | assert.is_true((image1 - image2):abs():max() < 10)
27 | end)
28 |
29 | it("can create image from memory source", function()
30 | local file = assert(io.open(JPEG_FILE, "rb"))
31 | local content = file:read("*a")
32 | file:close()
33 | local mem = ffi.new("unsigned char[?]", #content)
34 | ffi.copy(mem, content, #content)
35 | local source = vips.Source.new_from_memory(mem)
36 | local image = vips.Image.new_from_source(source, '', { access = 'sequential' })
37 | image:write_to_target(target, '.png')
38 |
39 | local image1 = vips.Image.new_from_file(JPEG_FILE, { access = 'sequential' })
40 | local image2 = vips.Image.new_from_file(TMP_FILE, { access = 'sequential' })
41 | assert.is_true((image1 - image2):abs():max() < 10)
42 | end)
43 | end)
44 | end)
45 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to `lua-vips` will be documented in this file.
4 |
5 | # master
6 |
7 | # 1.1-12 - 2025-02-09
8 |
9 | - add `vips.Connection`, `vips.Source` and `vips.Target` for true streaming support [rolandlo]
10 |
11 | # 1.1-11 - 2024-04-16
12 |
13 | - add standard Lua support [rolandlo]
14 | - add `vips.Interpolate` [jcupitt, rolandlo]
15 | - add `vips.concurrency_get()` and `set()` [kamyabzad]
16 | - add `hasalpha`/`addalpha` [RiskoZoSlovenska]
17 |
18 | # 1.1-10 - 2021-04-18
19 |
20 | - fix NYI warnings [kleisauke]
21 |
22 | # 1.1-9 - 2018-08-03
23 |
24 | - add `vips.leak_set()` [jcupitt]
25 | - add `soak.lua` example [jcupitt]
26 | - fix five minor memleaks [kleisauke]
27 | - update links for new home [jcupitt]
28 |
29 | # 1.1-8 - 2018-07-25
30 |
31 | - cleanups and some reorganisation [kleisauke]
32 | - fix regressions from 1.1-7 [kleisauke]
33 | - add `find_load` [kleisauke]
34 | - add `find_load_buffer` [kleisauke]
35 |
36 | # 1.1-7 - 2018-03-23
37 |
38 | - cleanups and some reorganisation
39 | - renamed cache control funcs, the names were missing the `cache_` prefix
40 | - fix `image:remove()` [kleisauke]
41 |
42 | # 1.1-6 - 2018-03-23
43 |
44 | - add operation cache control
45 |
46 | # 1.1-5 - 2017-10-09
47 |
48 | - add verror: handle libvips error buffer
49 | - add version: handle libvips version numbers
50 | - add `gvalue.to_enum`: wrap up enum encoding
51 | - add `composite`
52 | - add `new_from_memory`
53 | - add `write_to_memory`
54 | - remove `[]` and `#` overloads -- too confusing, and they broke debuggers
55 |
56 | # 1.1-4 - 2017-08-30
57 |
58 | - small doc fixes
59 | - fix get() on gobject enum properties with older libvips
60 | - test for gobject enum properties as strings
61 |
62 | # 1.1-3 - 2017-08-08
63 |
64 | - more Windows fixes
65 | - fix a callback leak with buffers, thanks wuyachao
66 |
67 | # 1.1-2 - 2017-07-21
68 |
69 | - fix "-" characters in arg names
70 |
71 | # 1.1-1 - 2017-06-19
72 |
73 | - tweaks to help LuaJIT on Windows
74 |
75 | # 1.0-1 - 2017-06-04
76 |
77 | - first API stable release
78 |
--------------------------------------------------------------------------------
/src/vips.lua:
--------------------------------------------------------------------------------
1 | -- top include for lua-vips
2 |
3 | local ffi = require "ffi"
4 |
5 | local vips_lib = ffi.load(ffi.os == "Windows" and "libvips-42.dll" or "vips")
6 |
7 | require "vips.cdefs"
8 |
9 | local result = vips_lib.vips_init("lua-vips")
10 | if result ~= 0 then
11 | local errstr = ffi.string(vips_lib.vips_error_buffer())
12 | vips_lib.vips_error_clear()
13 |
14 | error("unable to start up libvips: " .. errstr)
15 | end
16 |
17 | local vips = {
18 | verror = require "vips.verror",
19 | version = require "vips.version",
20 | log = require "vips.log",
21 | gvalue = require "vips.gvalue",
22 | vobject = require "vips.vobject",
23 | voperation = require "vips.voperation",
24 | Image = require "vips.Image_methods",
25 | Interpolate = require "vips.Interpolate",
26 | Connection = require "vips.Connection",
27 | Source = require "vips.Source",
28 | Target = require "vips.Target",
29 | }
30 |
31 | function vips.leak_set(leak)
32 | vips_lib.vips_leak_set(leak)
33 | end
34 |
35 | function vips.cache_set_max(max)
36 | vips_lib.vips_cache_set_max(max)
37 | end
38 |
39 | function vips.cache_get_max()
40 | return vips_lib.vips_cache_get_max()
41 | end
42 |
43 | function vips.cache_set_max_files(max)
44 | vips_lib.vips_cache_set_max_files(max)
45 | end
46 |
47 | function vips.cache_get_max_files()
48 | return vips_lib.vips_cache_get_max_files()
49 | end
50 |
51 | function vips.cache_set_max_mem(max)
52 | vips_lib.vips_cache_set_max_mem(max)
53 | end
54 |
55 | function vips.cache_get_max_mem()
56 | return vips_lib.vips_cache_get_max_mem()
57 | end
58 |
59 | function vips.concurrency_set(concurrency)
60 | return vips_lib.vips_concurrency_set(concurrency)
61 | end
62 |
63 | function vips.concurrency_get()
64 | return vips_lib.vips_concurrency_get()
65 | end
66 |
67 | function vips.type_find(basename, nickname)
68 | return vips_lib.vips_type_find(basename, nickname)
69 | end
70 |
71 | function vips.has(name)
72 | return vips.type_find("VipsOperation", name) ~= 0
73 | end
74 | -- for compat with 1.1-6, when these were misnamed
75 | vips.set_max = vips.cache_set_max
76 | vips.get_max = vips.cache_get_max
77 | vips.set_max_files = vips.cache_set_max_files
78 | vips.get_max_files = vips.cache_get_max_files
79 | vips.set_max_mem = vips.cache_set_max_mem
80 | vips.get_max_mem = vips.cache_get_max_mem
81 |
82 | return vips
83 |
--------------------------------------------------------------------------------
/src/vips/log.lua:
--------------------------------------------------------------------------------
1 | -- simple logging
2 |
3 | local logging_enabled = false
4 |
5 | local type = type
6 | local print = print
7 | local pairs = pairs
8 | local unpack = unpack or table.unpack
9 | local tostring = tostring
10 | local str_rep = string.rep
11 |
12 | local log = {}
13 | log = {
14 | enable = function(on)
15 | logging_enabled = on
16 | end,
17 |
18 | msg = function(...)
19 | if logging_enabled then
20 | print(unpack { ... })
21 | end
22 | end,
23 |
24 | prettyprint_table = function(p, table)
25 | local p_r_cache = {}
26 | local function sub_p_r(t, indent)
27 | if (p_r_cache[tostring(t)]) then
28 | p(indent .. "*" .. tostring(t))
29 | else
30 | p_r_cache[tostring(t)] = true
31 | if type(t) == "table" then
32 | for pos, val in pairs(t) do
33 | if type(val) == "table" then
34 | p(indent ..
35 | "[" .. pos .. "] => " .. tostring(t) .. " {")
36 | local length = type(pos) == "string" and #pos or pos
37 | sub_p_r(val, indent .. str_rep(" ", length + 8))
38 | p(indent .. str_rep(" ", length + 6) .. "}")
39 | elseif type(val) == "string" then
40 | p(indent .. "[" .. pos .. '] => "' ..
41 | val .. '"')
42 | else
43 | p(indent .. "[" .. pos .. "] => " ..
44 | tostring(val))
45 | end
46 | end
47 | else
48 | p(indent .. tostring(t))
49 | end
50 | end
51 | end
52 |
53 | if type(table) == "table" then
54 | p(tostring(table) .. " {")
55 | sub_p_r(table, " ")
56 | p("}")
57 | else
58 | sub_p_r(table, " ")
59 | end
60 | p()
61 | end,
62 |
63 | msg_r = function(t)
64 | if logging_enabled then
65 | log.prettyprint_table(log.msg, t)
66 | end
67 | end,
68 |
69 | print_r = function(t)
70 | if logging_enabled then
71 | log.prettyprint_table(print, t)
72 | end
73 | end
74 | }
75 |
76 | return log
77 |
--------------------------------------------------------------------------------
/spec/gvalue_spec.lua:
--------------------------------------------------------------------------------
1 | local vips = require "vips"
2 |
3 | -- test gvalue
4 | describe("test gvalue", function()
5 | local im, values
6 |
7 | setup(function()
8 | im = vips.Image.new_from_file("./spec/images/Gugg_coloured.jpg")
9 | values = im:bandsplit()
10 | -- vips.log.enable(true)
11 | end)
12 |
13 | it("can set/get an int-valued gvalue", function()
14 | local value = vips.gvalue()
15 | value:init(vips.gvalue.gint_type)
16 | value:set(12)
17 | assert.are.equal(12, value:get())
18 | end)
19 |
20 | it("can set/get a string-valued gvalue", function()
21 | local value = vips.gvalue()
22 | value:init(vips.gvalue.gstr_type)
23 | value:set("banana")
24 | assert.are.equal("banana", value:get())
25 | end)
26 |
27 | it("can set/get a bool-valued gvalue", function()
28 | local value = vips.gvalue()
29 | value:init(vips.gvalue.gbool_type)
30 | value:set(true)
31 | assert.are.equal(1, value:get())
32 | end)
33 |
34 | it("can set/get a double-valued gvalue", function()
35 | local value = vips.gvalue()
36 | value:init(vips.gvalue.gdouble_type)
37 | value:set(3.1415)
38 | assert.are.equal(3.1415, value:get())
39 | end)
40 |
41 | it("can set/get a enum-valued gvalue", function()
42 | if vips.version.at_least(8, 6) then
43 | local value = vips.gvalue()
44 | value:init(vips.gvalue.blend_mode_type)
45 | -- need to map str -> int by hand, since the mode arg is actually
46 | -- arrayint
47 | value:set(vips.gvalue.to_enum(vips.gvalue.blend_mode_type, 'over'))
48 | assert.are.equal('over', value:get())
49 | end
50 | end)
51 |
52 | it("can set/get a array-int-valued gvalue", function()
53 | local value = vips.gvalue()
54 | value:init(vips.gvalue.array_int_type)
55 | value:set({ 1, 2, 3 })
56 | assert.are.same({ 1, 2, 3 }, value:get())
57 | end)
58 |
59 | it("can set/get a array-double-valued gvalue", function()
60 | local value = vips.gvalue()
61 | value:init(vips.gvalue.array_double_type)
62 | value:set({ 1.1, 2.1, 3.1 })
63 | assert.are.same({ 1.1, 2.1, 3.1 }, value:get())
64 | end)
65 |
66 | it("can set/get a image-valued gvalue", function()
67 | local value = vips.gvalue()
68 | value:init(vips.gvalue.image_type)
69 | value:set(im)
70 | assert.are.same(im, value:get())
71 | end)
72 |
73 | it("can set/get a array-image-valued gvalue", function()
74 | local value = vips.gvalue()
75 | value:init(vips.gvalue.array_image_type)
76 | value:set(values)
77 | assert.are.same(values, value:get())
78 | end)
79 | end)
80 |
--------------------------------------------------------------------------------
/spec/meta_spec.lua:
--------------------------------------------------------------------------------
1 | local vips = require "vips"
2 | local ffi = require "ffi"
3 |
4 | local UHDR_FILE = "./spec/images/uhdr.jpg"
5 | -- test metadata read/write
6 | describe("metadata", function()
7 | local array, im
8 | local tmp_vips_filename = ffi.os == "Windows" and os.getenv("TMP") .. "\\x.v" or "/tmp/x.v"
9 |
10 | setup(function()
11 | array = { 1, 2, 3, 4 }
12 | im = vips.Image.new_from_array(array)
13 | -- vips.log.enable(true)
14 | end)
15 |
16 | teardown(function()
17 | os.remove(tmp_vips_filename)
18 | end)
19 |
20 | it("can set/get int", function()
21 | local im2 = im:copy()
22 |
23 | im2:set_type(vips.gvalue.gint_type, "banana", 12)
24 | assert.are.equal(im2:get("banana"), 12)
25 | end)
26 |
27 | it("can remove metadata", function()
28 | local im2 = im:copy()
29 |
30 | im2:set_type(vips.gvalue.gint_type, "banana", 12)
31 | im2:remove("banana")
32 | assert.are.equal(im2:get_typeof("banana"), 0)
33 | end)
34 |
35 | it("can set/get double", function()
36 | local im2 = im:copy()
37 |
38 | im2:set_type(vips.gvalue.gdouble_type, "banana", 3.1415)
39 | assert.are.equal(im2:get("banana"), 3.1415)
40 | end)
41 |
42 | it("can set/get string", function()
43 | local im2 = im:copy()
44 |
45 | im2:set_type(vips.gvalue.gstr_type, "banana", "tasty one")
46 | assert.are.same(im2:get("banana"), "tasty one")
47 | end)
48 |
49 | it("can set/get through vips file save/load", function()
50 | local im2 = im:copy()
51 |
52 | im2:set_type(vips.gvalue.gint_type, "banana", 12)
53 | im2:write_to_file(tmp_vips_filename)
54 | local im3 = vips.Image.new_from_file(tmp_vips_filename)
55 | assert.are.same(im3:get("banana"), im2:get("banana"))
56 | end)
57 |
58 | it("can get property enums as strings", function()
59 | local im2 = im:copy()
60 |
61 | assert.are.same(im2:format(), "double")
62 | end)
63 |
64 | it("can set gainmap", function()
65 | if vips.version.at_least(8, 18) and vips.has("uhdrload") then
66 | local function crop_gainmap(image)
67 | local gainmap = image:get_gainmap()
68 |
69 | if gainmap then
70 | local new_gainmap = gainmap:crop(0, 0, 10, 10)
71 | image = image:copy()
72 | image:set_type(vips.gvalue.image_type, "gainmap", new_gainmap)
73 | end
74 |
75 | return image
76 | end
77 |
78 | local image = vips.Image.new_from_file(UHDR_FILE)
79 | image = crop_gainmap(image)
80 | local buf = image:write_to_buffer(".jpg")
81 | local new_image = vips.Image.new_from_buffer(buf, "")
82 | local new_gainmap = new_image:get_gainmap()
83 |
84 | assert.are.same(new_gainmap:width(), 10)
85 | end
86 | end)
87 | end)
88 |
--------------------------------------------------------------------------------
/src/vips/vobject.lua:
--------------------------------------------------------------------------------
1 | -- manage VipsObject
2 | -- abstract base class for voperation and vimage
3 |
4 | local ffi = require "ffi"
5 |
6 | local verror = require "vips.verror"
7 | local log = require "vips.log"
8 | local gvalue = require "vips.gvalue"
9 |
10 | local print = print
11 | local error = error
12 | local collectgarbage = collectgarbage
13 |
14 | local vips_lib
15 | local gobject_lib
16 | if ffi.os == "Windows" then
17 | vips_lib = ffi.load("libvips-42.dll")
18 | gobject_lib = ffi.load("libgobject-2.0-0.dll")
19 | else
20 | vips_lib = ffi.load("vips")
21 | gobject_lib = vips_lib
22 | end
23 |
24 | local vobject = {}
25 |
26 | -- types to get ref back from vips_object_get_argument()
27 | vobject.typeof = ffi.typeof("VipsObject*")
28 | vobject.pspec_typeof = ffi.typeof("GParamSpec*[1]")
29 | vobject.argument_class_typeof = ffi.typeof("VipsArgumentClass*[1]")
30 | vobject.argument_instance_typeof = ffi.typeof("VipsArgumentInstance*[1]")
31 |
32 | vobject.print_all = function(msg)
33 | collectgarbage()
34 | print(msg)
35 | vips_lib.vips_object_print_all()
36 | print()
37 | end
38 |
39 | vobject.new = function(pt)
40 | return ffi.gc(pt, gobject_lib.g_object_unref)
41 | end
42 |
43 | -- return 0 for not found and leave the error in the error log
44 | vobject.get_typeof = function(self, name)
45 | local pspec = vobject.pspec_typeof()
46 | local argument_class = vobject.argument_class_typeof()
47 | local argument_instance = vobject.argument_instance_typeof()
48 | local result = vips_lib.vips_object_get_argument(self, name,
49 | pspec, argument_class, argument_instance)
50 |
51 | if result ~= 0 then
52 | return 0
53 | end
54 |
55 | return pspec[0].value_type
56 | end
57 |
58 | vobject.get_type = function(self, name, gtype)
59 | log.msg("vobject.get_type")
60 | log.msg(" name =", name)
61 |
62 | if gtype == 0 then
63 | return false
64 | end
65 |
66 | local pgv = gvalue(true)
67 | pgv[0]:init(gtype)
68 |
69 | -- this will add a ref for GObject properties, that ref will be
70 | -- unreffed when the gvalue is finalized
71 | gobject_lib.g_object_get_property(self, name, pgv)
72 |
73 | local result = pgv[0]:get()
74 | gobject_lib.g_value_unset(pgv[0])
75 |
76 | return result
77 | end
78 |
79 | vobject.set_type = function(self, name, value, gtype)
80 | log.msg("vobject.set_type")
81 | log.msg(" name =", name)
82 | log.msg(" value =", value)
83 |
84 | if gtype == 0 then
85 | return false
86 | end
87 |
88 | local pgv = gvalue(true)
89 | pgv[0]:init(gtype)
90 | pgv[0]:set(value)
91 | gobject_lib.g_object_set_property(self, name, pgv)
92 | gobject_lib.g_value_unset(pgv[0])
93 |
94 | return true
95 | end
96 |
97 | vobject.get = function(self, name)
98 | log.msg("vobject.get")
99 | log.msg(" name =", name)
100 |
101 | local gtype = self:get_typeof(name)
102 | if gtype == 0 then
103 | error(verror.get())
104 | end
105 |
106 | return vobject.get_type(self, name, gtype)
107 | end
108 |
109 | vobject.set = function(self, name, value)
110 | log.msg("vobject.set")
111 | log.msg(" name =", name)
112 | log.msg(" value =", value)
113 |
114 | local gtype = self:get_typeof(name)
115 | if gtype == 0 then
116 | error(verror.get())
117 | end
118 |
119 | vobject.set_type(self, name, value, gtype)
120 |
121 | return true
122 | end
123 |
124 | return ffi.metatype("VipsObject", {
125 |
126 | -- no __gc method, we don't build these things ourselves, just wrap the
127 | -- pointer, so we use ffi.gc() instead
128 |
129 | __index = vobject
130 | })
131 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: "CI"
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | unix:
7 | strategy:
8 | fail-fast: false
9 | matrix:
10 | luaVersion: ["5.1", "5.2", "5.3", "5.4", "luajit-openresty"]
11 | os: ["ubuntu-latest", "macos-latest"]
12 |
13 | runs-on: ${{ matrix.os }}
14 |
15 | steps:
16 | - uses: actions/checkout@v6
17 |
18 | - uses: leafo/gh-actions-lua@v12
19 | with:
20 | luaVersion: ${{ matrix.luaVersion }}
21 |
22 | - uses: leafo/gh-actions-luarocks@v6
23 |
24 | - name: Install Ubuntu dependencies
25 | if: runner.os == 'Linux'
26 | run: |
27 | sudo apt-get update
28 | sudo apt-get install --no-install-recommends libvips-dev
29 |
30 | - name: Install macOS dependencies
31 | if: runner.os == 'macOS'
32 | run: |
33 | brew install vips
34 | echo "DYLD_LIBRARY_PATH=$(brew --prefix vips)/lib:$DYLD_LIBRARY_PATH" >> $GITHUB_ENV
35 |
36 | - name: Prepare LuaJIT environment
37 | if: startsWith(matrix.luaVersion, 'luajit')
38 | run: luarocks config --scope system rocks_provided.luaffi-tkl 2.1-1
39 |
40 | - name: Install lua-vips
41 | run: luarocks make
42 |
43 | - name: Lint with luacheck
44 | run: |
45 | luarocks install luacheck
46 | luacheck -q .
47 |
48 | - name: Busted tests
49 | run: luarocks test spec/ -- -o gtest -v spec
50 |
51 | windows:
52 | strategy:
53 | fail-fast: false
54 | matrix:
55 | include:
56 | # Two tests are failing
57 | #- lua: { version: "5.1", msys2-name: "lua51", exe: "lua5.1" }
58 | - lua: { version: "5.3", msys2-name: "lua53", exe: "lua5.3" }
59 | - lua: { version: "5.4", msys2-name: "lua", exe: "lua" }
60 | - lua: { version: "5.1", msys2-name: "luajit", exe: "luajit" }
61 |
62 | runs-on: windows-latest
63 | defaults:
64 | run:
65 | shell: msys2 {0}
66 |
67 | steps:
68 | - uses: actions/checkout@v6
69 |
70 | - uses: msys2/setup-msys2@v2
71 | with:
72 | msystem: mingw64
73 | update: true
74 | install: >-
75 | git
76 | make
77 | mingw-w64-x86_64-toolchain
78 | mingw-w64-x86_64-libvips
79 | mingw-w64-x86_64-openslide
80 | mingw-w64-x86_64-libheif
81 | mingw-w64-x86_64-libjxl
82 | mingw-w64-x86_64-imagemagick
83 | mingw-w64-x86_64-poppler
84 | mingw-w64-x86_64-lua-luarocks
85 | mingw-w64-x86_64-${{ matrix.lua.msys2-name }}
86 |
87 | - name: Prepare LuaRocks config for Lua 5.3
88 | if: matrix.lua.exe == 'lua5.3'
89 | run: cp /mingw64/etc/luarocks/config-5.{4,3}.lua
90 |
91 | - name: Prepare LuaRocks
92 | run: |
93 | luarocks config --scope system lua_version ${{ matrix.lua.version }}
94 | luarocks config --scope system lua_interpreter ${{ matrix.lua.exe }}.exe
95 | luarocks config --scope system lua_dir /mingw64
96 |
97 | - name: Prepare LuaJIT environment
98 | if: matrix.lua.exe == 'luajit'
99 | run: luarocks config --scope system rocks_provided.luaffi-tkl 2.1-1
100 |
101 | - name: Add local LuaRocks bin to PATH
102 | run: echo "$HOME/.luarocks/bin" >> $GITHUB_PATH
103 |
104 | - name: Install lua-vips
105 | run: luarocks make
106 |
107 | - name: Lint with luacheck
108 | run: |
109 | luarocks install luacheck
110 | luacheck.bat -q .
111 |
112 | - name: Busted tests
113 | run: luarocks test spec -- --lua=${{ matrix.lua.exe }} -o gtest -v
114 |
--------------------------------------------------------------------------------
/spec/convenience_spec.lua:
--------------------------------------------------------------------------------
1 | local vips = require "vips"
2 |
3 | -- test convenience functions
4 | describe("test convenience functions", function()
5 | local array, im
6 |
7 | setup(function()
8 | array = { 1, 2, 3, 4 }
9 | im = vips.Image.new_from_array(array)
10 | -- vips.log.enable(true)
11 | end)
12 |
13 | it("can join one image bandwise", function()
14 | local im2 = im:bandjoin(im)
15 |
16 | assert.are.equal(im2:width(), 4)
17 | assert.are.equal(im2:height(), 1)
18 | assert.are.equal(im2:bands(), 2)
19 | assert.are.equal(im2:extract_band(0):avg(), 2.5)
20 | assert.are.equal(im2:extract_band(1):avg(), 2.5)
21 | end)
22 |
23 | it("can join images bandwise", function()
24 | local im2 = im:bandjoin { im + 1, im + 2 }
25 |
26 | assert.are.equal(im2:width(), 4)
27 | assert.are.equal(im2:height(), 1)
28 | assert.are.equal(im2:bands(), 3)
29 | assert.are.equal(im2:extract_band(0):avg(), 2.5)
30 | assert.are.equal(im2:extract_band(1):avg(), 3.5)
31 | assert.are.equal(im2:extract_band(2):avg(), 4.5)
32 | end)
33 |
34 | it("can join constants to images bandwise", function()
35 | local im2 = im:bandjoin(255)
36 |
37 | assert.are.equal(im2:width(), 4)
38 | assert.are.equal(im2:height(), 1)
39 | assert.are.equal(im2:bands(), 2)
40 | assert.are.equal(im2:extract_band(0):avg(), 2.5)
41 | assert.are.equal(im2:extract_band(1):avg(), 255)
42 | end)
43 |
44 | it("can join images and constants bandwise", function()
45 | local im2 = im:bandjoin { im + 1, 255, im + 2 }
46 |
47 | assert.are.equal(im2:width(), 4)
48 | assert.are.equal(im2:height(), 1)
49 | assert.are.equal(im2:bands(), 4)
50 | assert.are.equal(im2:extract_band(0):avg(), 2.5)
51 | assert.are.equal(im2:extract_band(1):avg(), 3.5)
52 | assert.are.equal(im2:extract_band(2):avg(), 255)
53 | assert.are.equal(im2:extract_band(3):avg(), 4.5)
54 | end)
55 |
56 | it("can join images and array constants bandwise", function()
57 | local im2 = im:bandjoin { im + 1, { 255, 128 } }
58 |
59 | assert.are.equal(im2:width(), 4)
60 | assert.are.equal(im2:height(), 1)
61 | assert.are.equal(im2:bands(), 4)
62 | assert.are.equal(im2:extract_band(0):avg(), 2.5)
63 | assert.are.equal(im2:extract_band(1):avg(), 3.5)
64 | assert.are.equal(im2:extract_band(2):avg(), 255)
65 | assert.are.equal(im2:extract_band(3):avg(), 128)
66 | end)
67 |
68 | it("can call composite", function()
69 | if vips.version.at_least(8, 6) then
70 | local base = (im + { 10, 11, 12 }):copy { interpretation = "srgb" }
71 | local overlay = (base + 10):bandjoin(128)
72 | local comp = base:composite(overlay, "over")
73 | local pixel = comp:getpoint(0, 0)
74 |
75 | assert.are.equal(comp:width(), 4)
76 | assert.are.equal(comp:height(), 1)
77 | assert.are.equal(comp:bands(), 4)
78 | assert.is_true(math.abs(pixel[1] - 16) < 0.1)
79 | assert.is_true(math.abs(pixel[2] - 17) < 0.1)
80 | assert.is_true(math.abs(pixel[3] - 18) < 0.1)
81 | assert.are.equal(pixel[4], 255)
82 | end
83 | end)
84 |
85 | it("can call bandrank", function()
86 | local im2 = im:bandrank(im + 1, { index = 0 })
87 |
88 | assert.are.equal(im2:width(), 4)
89 | assert.are.equal(im2:height(), 1)
90 | assert.are.equal(im2:bands(), 1)
91 | assert.are.equal(im2:extract_band(0):avg(), 2.5)
92 | end)
93 |
94 | it("can call bandsplit", function()
95 | local bands = im:bandjoin { im + 1, { 255, 128 } }:bandsplit()
96 |
97 | assert.are.equal(#bands, 4)
98 | assert.are.equal(bands[1]:width(), 4)
99 | assert.are.equal(bands[1]:height(), 1)
100 | assert.are.equal(bands[1]:bands(), 1)
101 | end)
102 |
103 | it("can call ifthenelse with an image and two constants", function()
104 | local result = im:more(2):ifthenelse(1, 2)
105 |
106 | assert.are.equal(result:width(), 4)
107 | assert.are.equal(result:height(), 1)
108 | assert.are.equal(result:bands(), 1)
109 | assert.are.equal(result:avg(), 6 / 4)
110 | end)
111 |
112 | it("can call ifthenelse with two images and one constant", function()
113 | local result = im:more(2):ifthenelse(im + 3, 2)
114 |
115 | assert.are.equal(result:width(), 4)
116 | assert.are.equal(result:height(), 1)
117 | assert.are.equal(result:bands(), 1)
118 | assert.are.equal(result:avg(), 17 / 4)
119 | end)
120 |
121 | it("can call hasalpha", function()
122 | local im1 = vips.Image.new_from_file("./spec/images/Gugg_coloured.jpg")
123 | local im2 = vips.Image.new_from_file("./spec/images/watermark.png")
124 |
125 | assert.is_false(im1:hasalpha())
126 | assert.is_true(im2:hasalpha())
127 | end)
128 |
129 | it("can call addalpha", function ()
130 | assert.are.equal(im:addalpha():avg(), 128.75)
131 | end)
132 | end)
133 |
--------------------------------------------------------------------------------
/spec/write_spec.lua:
--------------------------------------------------------------------------------
1 | local ffi = require("ffi")
2 | local vips = require "vips"
3 |
4 | -- test image writers
5 | describe("test image write", function()
6 |
7 | setup(function()
8 | -- vips.log.enable(true)
9 | end)
10 |
11 | describe("to file", function()
12 | local im = vips.Image.black(100, 100)
13 | local tmp_png_filename = ffi.os == "Windows" and os.getenv("TMP") .. "\\x.png" or "/tmp/x.png"
14 | local tmp_jpg_filename = ffi.os == "Windows" and os.getenv("TMP") .. "\\x.jpg" or "/tmp/x.jpg"
15 |
16 | teardown(function()
17 | os.remove(tmp_png_filename)
18 | os.remove(tmp_jpg_filename)
19 | end)
20 |
21 | it("can save and then load a png", function()
22 | im:write_to_file(tmp_png_filename)
23 | local im2 = vips.Image.new_from_file(tmp_png_filename)
24 |
25 | assert.are.equal(im:width(), im2:width())
26 | assert.are.equal(im:height(), im2:height())
27 | assert.are.equal(im:avg(), im2:avg())
28 | end)
29 |
30 | it("can save and then load a jpg with an option", function()
31 | im:write_to_file(tmp_jpg_filename, { Q = 90 })
32 | local im2 = vips.Image.new_from_file(tmp_jpg_filename)
33 |
34 | assert.are.equal(im:width(), im2:width())
35 | assert.are.equal(im:height(), im2:height())
36 | assert.are.almost_equal(im:avg(), im2:avg())
37 | end)
38 | end)
39 |
40 | describe("to buffer", function()
41 | it("can write a jpeg to buffer", function()
42 | local im = vips.Image.new_from_file("./spec/images/Gugg_coloured.jpg")
43 | local buf = im:write_to_buffer(".jpg")
44 | local f = io.open("x.jpg", "w+b")
45 | f:write(buf)
46 | f:close()
47 | local im2 = vips.Image.new_from_file("x.jpg")
48 |
49 | assert.are.equal(im:width(), im2:width())
50 | assert.are.equal(im:height(), im2:height())
51 | assert.are.equal(im:format(), im2:format())
52 | assert.are.equal(im:xres(), im2:xres())
53 | assert.are.equal(im:yres(), im2:yres())
54 | -- remove test file
55 | os.remove("x.jpg")
56 | end)
57 |
58 | it("can write a jpeg to buffer with an option", function()
59 | local im = vips.Image.new_from_file("./spec/images/Gugg_coloured.jpg")
60 | local buf = im:write_to_buffer(".jpg")
61 | local buf2 = im:write_to_buffer(".jpg", { Q = 100 })
62 |
63 | assert.is.True(#buf2 > #buf)
64 | end)
65 |
66 | it("can write an image to a memory area", function()
67 | local im = vips.Image.new_from_file("./spec/images/Gugg_coloured.jpg")
68 | local mem = im:write_to_memory()
69 |
70 | assert.are.equal(im:width() * im:height() * 3, ffi.sizeof(mem))
71 | end)
72 |
73 | it("can read an image back from a memory area", function()
74 | local im = vips.Image.new_from_file("./spec/images/Gugg_coloured.jpg")
75 | local mem = im:write_to_memory()
76 | assert.are.equal(im:width() * im:height() * 3, ffi.sizeof(mem))
77 | local im2 = vips.Image.new_from_memory(mem,
78 | im:width(), im:height(), im:bands(), im:format())
79 |
80 | assert.are.equal(im:avg(), im2:avg())
81 | end)
82 |
83 | it("can write an image to a memory area (no copy)", function()
84 | local im = vips.Image.new_from_file("./spec/images/Gugg_coloured.jpg")
85 | local _, size = im:write_to_memory_ptr()
86 |
87 | assert.are.equal(im:width() * im:height() * 3, size)
88 | end)
89 |
90 | it("can read an image back from a memory area (no copy)", function()
91 | local im = vips.Image.new_from_file("./spec/images/Gugg_coloured.jpg")
92 | local ptr, size = im:write_to_memory_ptr()
93 | assert.are.equal(im:width() * im:height() * 3, size)
94 | local im2 = vips.Image.new_from_memory_ptr(ptr,
95 | size, im:width(), im:height(), im:bands(), im:format())
96 |
97 | assert.are.equal(im:avg(), im2:avg())
98 | end)
99 | end)
100 |
101 | describe("MODIFY args", function()
102 | it("can draw a circle on an image", function()
103 | local im = vips.Image.black(101, 101)
104 | local im2 = im:draw_circle(255, 50, 50, 50, { fill = true })
105 |
106 | assert.are.equal(im2:width(), 101)
107 | assert.are.equal(im2:height(), 101)
108 | assert.are.almost_equal(im2:avg(), 255 * 3.1415927 / 4, 0.2)
109 | end)
110 |
111 | it("each draw op makes a new image", function()
112 | local im = vips.Image.black(101, 101)
113 | local im2 = im:draw_circle(255, 50, 50, 50, { fill = true })
114 | local im3 = im2:draw_circle(0, 50, 50, 40, { fill = true })
115 |
116 | assert.are.equal(im2:width(), 101)
117 | assert.are.equal(im2:height(), 101)
118 | assert.are.almost_equal(im2:avg(), 255 * 3.1415927 / 4, 0.2)
119 | assert.is.True(im3:avg() < im2:avg())
120 | end)
121 | end)
122 | end)
123 |
--------------------------------------------------------------------------------
/spec/overloads_spec.lua:
--------------------------------------------------------------------------------
1 | local vips = require "vips"
2 |
3 | -- test all operator overloads
4 |
5 | -- make a table of x repeated n times
6 | local function replicate(x, n)
7 | local result = {}
8 |
9 | for i = 1, n do
10 | result[i] = x
11 | end
12 |
13 | return result
14 | end
15 |
16 | -- apply an operation to a nested table, or to a number
17 | local function map(op, a)
18 | local result
19 |
20 | if type(a) == "table" then
21 | result = {}
22 | for i = 1, #a do
23 | result[i] = map(op, a[i])
24 | end
25 | else
26 | result = op(a)
27 | end
28 |
29 | return result
30 | end
31 |
32 | -- apply an operation pairwise to two nested tables, or to a table and a number,
33 | -- or to two numbers
34 | local function map2(op, a, b)
35 | if type(a) == "table" and type(b) == "table" then
36 | assert.are.equal(#a, #b)
37 | end
38 |
39 | local result
40 | if type(a) == "table" or type(b) == "table" then
41 | if type(a) ~= "table" then
42 | a = replicate(a, #b)
43 | end
44 |
45 | if type(b) ~= "table" then
46 | b = replicate(b, #a)
47 | end
48 |
49 | result = {}
50 | for i = 1, #a do
51 | result[i] = map2(op, a[i], b[i])
52 | end
53 | else
54 | result = op(a, b)
55 | end
56 |
57 | return result
58 | end
59 |
60 | -- find the sum and number of elements in a nested table
61 | local function sum(a)
62 | local total = 0
63 | local n = 0
64 |
65 | if type(a) == "table" then
66 | for i = 1, #a do
67 | local new_total
68 | local new_n
69 |
70 | new_total, new_n = sum(a[i])
71 |
72 | total = total + new_total
73 | n = n + new_n
74 | end
75 | else
76 | total = a
77 | n = 1
78 | end
79 |
80 | return total, n
81 | end
82 |
83 | -- find the average of a nested table
84 | local function avg(a)
85 | local total
86 | local n
87 |
88 | total, n = sum(a)
89 |
90 | return total / n
91 | end
92 |
93 | local function test_binary(name, vop, lop)
94 | local array, im
95 |
96 | setup(function()
97 | array = { 1, 2, 3, 4 }
98 | im = vips.Image.new_from_array(array)
99 | end)
100 |
101 | describe(name, function()
102 |
103 | it("can " .. name .. " image and single constant", function()
104 | local im2 = vop(im, 12)
105 | local a2 = map2(lop, array, 12)
106 |
107 | assert.are.equal(im2:width(), 4)
108 | assert.are.equal(im2:height(), 1)
109 | assert.are.equal(im2:bands(), 1)
110 | assert.are.almost_equal(im2:avg(), avg(a2))
111 | end)
112 |
113 | it("can " .. name .. " image and single constant, reversed", function()
114 | local im2 = vop(12, im)
115 | local a2 = map2(lop, 12, array)
116 |
117 | assert.are.equal(im2:width(), 4)
118 | assert.are.equal(im2:height(), 1)
119 | assert.are.equal(im2:bands(), 1)
120 | assert.are.almost_equal(im2:avg(), avg(a2))
121 | end)
122 |
123 | it("can " .. name .. " an image and an array", function()
124 | local array_constant = { 12, 13, 14 }
125 | local im2 = vop(im, array_constant)
126 | local a2 = map2(lop, array, replicate(array_constant, #array))
127 |
128 | assert.are.equal(im2:width(), 4)
129 | assert.are.equal(im2:height(), 1)
130 | assert.are.equal(im2:bands(), 3)
131 | assert.are.almost_equal(im2:avg(), avg(a2))
132 | end)
133 |
134 | it("can " .. name .. " an image and an array, reversed", function()
135 | local array_constant = { 12, 13, 14 }
136 | local im2 = vop(array_constant, im)
137 | local a2 = map2(lop, replicate(array_constant, #array), array)
138 |
139 | assert.are.equal(im2:width(), 4)
140 | assert.are.equal(im2:height(), 1)
141 | assert.are.equal(im2:bands(), 3)
142 | assert.are.almost_equal(im2:avg(), avg(a2))
143 | end)
144 |
145 | it("can " .. name .. " two images", function()
146 | local im2 = vop(im, im)
147 | local a2 = map2(lop, array, array)
148 |
149 | assert.are.equal(im2:width(), 4)
150 | assert.are.equal(im2:height(), 1)
151 | assert.are.equal(im2:bands(), 1)
152 | assert.are.almost_equal(im2:avg(), avg(a2))
153 | end)
154 | end)
155 | end
156 |
157 | local function test_binary_noreverse(name, vop, lop)
158 | local array, im
159 |
160 | setup(function()
161 | array = { 1, 2, 3, 4 }
162 | im = vips.Image.new_from_array(array)
163 | end)
164 |
165 | describe(name, function()
166 | it("can " .. name .. " image and single constant", function()
167 | local im2 = vop(im, 12)
168 | local a2 = map2(lop, array, 12)
169 |
170 | assert.are.equal(im2:width(), 4)
171 | assert.are.equal(im2:height(), 1)
172 | assert.are.equal(im2:bands(), 1)
173 | assert.are.almost_equal(im2:avg(), avg(a2))
174 | end)
175 |
176 | it("can " .. name .. " an image and an array", function()
177 | local im2 = vop(im, { 12, 13, 14 })
178 | local a2 = map2(lop, array, 13)
179 |
180 | assert.are.equal(im2:width(), 4)
181 | assert.are.equal(im2:height(), 1)
182 | assert.are.equal(im2:bands(), 3)
183 | assert.are.almost_equal(im2:avg(), avg(a2))
184 | end)
185 |
186 | it("can " .. name .. " two images", function()
187 | local im2 = vop(im, im)
188 | local a2 = map2(lop, array, array)
189 |
190 | assert.are.equal(im2:width(), 4)
191 | assert.are.equal(im2:height(), 1)
192 | assert.are.equal(im2:bands(), 1)
193 | assert.are.almost_equal(im2:avg(), avg(a2))
194 | end)
195 | end)
196 | end
197 |
198 | local function test_unary(name, vop, lop)
199 | local array, im
200 |
201 | setup(function()
202 | array = { 1, 2, 3, 4 }
203 | im = vips.Image.new_from_array(array)
204 | end)
205 |
206 | describe(name, function()
207 | it("can " .. name .. " an image", function()
208 | local im2 = vop(im)
209 | local a2 = map(lop, array)
210 |
211 | assert.are.equal(im2:width(), 4)
212 | assert.are.equal(im2:height(), 1)
213 | assert.are.equal(im2:bands(), 1)
214 | assert.are.almost_equal(im2:avg(), avg(a2))
215 | end)
216 | end)
217 | end
218 |
219 | describe("test overload", function()
220 | test_binary("add",
221 | function(a, b)
222 | return vips.Image.mt.__add(a, b)
223 | end,
224 | function(a, b)
225 | return a + b
226 | end)
227 |
228 | test_binary("sub",
229 | function(a, b)
230 | return vips.Image.mt.__sub(a, b)
231 | end,
232 | function(a, b)
233 | return a - b
234 | end)
235 |
236 | test_binary("mul",
237 | function(a, b)
238 | return vips.Image.mt.__mul(a, b)
239 | end,
240 | function(a, b)
241 | return a * b
242 | end)
243 |
244 | test_binary("div",
245 | function(a, b)
246 | return vips.Image.mt.__div(a, b)
247 | end,
248 | function(a, b)
249 | return a / b
250 | end)
251 |
252 | test_binary_noreverse("mod",
253 | function(a, b)
254 | return vips.Image.mt.__mod(a, b)
255 | end,
256 | function(a, b)
257 | return a % b
258 | end)
259 |
260 | test_binary("pow",
261 | function(a, b)
262 | return vips.Image.mt.__pow(a, b)
263 | end,
264 | function(a, b)
265 | return a ^ b
266 | end)
267 |
268 | test_unary("unm",
269 | function(a)
270 | return vips.Image.mt.__unm(a)
271 | end,
272 | function(a)
273 | return -a
274 | end)
275 |
276 | describe("band overloads", function()
277 | local array, im, im2
278 |
279 | setup(function()
280 | array = { 1, 2, 3, 4 }
281 | im = vips.Image.new_from_array(array)
282 | im2 = im:bandjoin({ im + 1, im + 2 })
283 | end)
284 |
285 | it("can bandjoin with '..'", function()
286 | local b = im .. im2
287 |
288 | assert.are.equal(b:width(), 4)
289 | assert.are.equal(b:height(), 1)
290 | assert.are.equal(b:bands(), 4)
291 | assert.are.equal(b:extract_band(0):avg(), 2.5)
292 | end)
293 | end)
294 |
295 | describe("call overload", function()
296 | local array, im, im2
297 |
298 | setup(function()
299 | array = { 1, 2, 3, 4 }
300 | im = vips.Image.new_from_array(array)
301 | im2 = im:bandjoin({ im + 1, im + 2 })
302 | end)
303 |
304 | it("can extract a pixel with '()'", function()
305 | local a, b, c = im2(1, 0)
306 |
307 | assert.are.equal(a, 2)
308 | assert.are.equal(b, 3)
309 | assert.are.equal(c, 4)
310 | end)
311 | end)
312 | end)
313 |
--------------------------------------------------------------------------------
/spec/new_spec.lua:
--------------------------------------------------------------------------------
1 | local ffi = require("ffi")
2 | ffi.cdef[[
3 | void* malloc(size_t size);
4 | void free(void *ptr);
5 | ]]
6 | local vips = require "vips"
7 |
8 | -- test image new/load/etc.
9 | describe("test image creation", function()
10 | setup(function()
11 | -- vips.log.enable(true)
12 | end)
13 |
14 | describe("test image from array", function()
15 | it("can make an image from a 1D array", function()
16 | local array = { 1, 2, 3, 4 }
17 | local im = vips.Image.new_from_array(array)
18 |
19 | assert.are.equal(im:width(), 4)
20 | assert.are.equal(im:height(), 1)
21 | assert.are.equal(im:get("scale"), 1)
22 | assert.are.equal(im:get("offset"), 0)
23 | assert.are.equal(im:avg(), 2.5)
24 | end)
25 |
26 | it("can make an image from a 2D array", function()
27 | local array = { { 1, 2 }, { 3, 4 } }
28 | local im = vips.Image.new_from_array(array)
29 |
30 | assert.are.equal(im:width(), 2)
31 | assert.are.equal(im:height(), 2)
32 | assert.are.equal(im:get("scale"), 1)
33 | assert.are.equal(im:get("offset"), 0)
34 | assert.are.equal(im:avg(), 2.5)
35 | end)
36 |
37 | it("can set scale and offset on an array", function()
38 | local array = { { 1, 2 }, { 3, 4 } }
39 | local im = vips.Image.new_from_array(array, 12, 3)
40 |
41 | assert.are.equal(im:width(), 2)
42 | assert.are.equal(im:height(), 2)
43 | assert.are.equal(im:get("scale"), 12)
44 | assert.are.equal(im:get("offset"), 3)
45 | assert.are.equal(im:avg(), 2.5)
46 | end)
47 | end)
48 |
49 | describe("test image from file", function()
50 | it("can load a jpeg from a file", function()
51 | local im = vips.Image.new_from_file("./spec/images/Gugg_coloured.jpg")
52 |
53 | assert.are.equal(im:width(), 972)
54 | assert.are.equal(im:height(), 1296)
55 | assert.are.almost_equal(im:avg(), 113.96)
56 | end)
57 |
58 | it("throws error when file does not exits", function()
59 | assert.has_error(function()
60 | vips.Image.new_from_file("/path/does/not/exist/unknown.jpg")
61 | end, "VipsForeignLoad: file \"/path/does/not/exist/unknown.jpg\" does not exist\n")
62 | end)
63 |
64 | it("can subsample a jpeg from a file", function()
65 | local im = vips.Image.new_from_file("./spec/images/Gugg_coloured.jpg",
66 | { shrink = 2 })
67 |
68 | assert.are.equal(im:width(), 486)
69 | assert.are.equal(im:height(), 648)
70 | assert.are.almost_equal(im:avg(), 113.979)
71 | end)
72 |
73 | it("can subsample a jpeg from a file, shrink in filename", function()
74 | local im = vips.Image.new_from_file("./spec/images/Gugg_coloured.jpg[shrink=2]")
75 |
76 | assert.are.equal(im:width(), 486)
77 | assert.are.equal(im:height(), 648)
78 | assert.are.almost_equal(im:avg(), 113.979)
79 | end)
80 | end)
81 |
82 | describe("test image from buffer", function()
83 | it("can write a jpeg to buffer", function()
84 | local im = vips.Image.new_from_file("./spec/images/Gugg_coloured.jpg")
85 | local buf = im:write_to_buffer(".jpg")
86 | local f = io.open("x.jpg", "w+b")
87 | f:write(buf)
88 | f:close()
89 | local im2 = vips.Image.new_from_file("x.jpg")
90 |
91 | assert.are.equal(im:width(), im2:width())
92 | assert.are.equal(im:height(), im2:height())
93 | assert.are.equal(im:format(), im2:format())
94 | assert.are.equal(im:xres(), im2:xres())
95 | assert.are.equal(im:yres(), im2:yres())
96 | -- remove test file
97 | os.remove("x.jpg")
98 | end)
99 |
100 | it("can load a jpeg from a buffer", function()
101 | local im = vips.Image.new_from_file("./spec/images/Gugg_coloured.jpg")
102 | local f = io.open("./spec/images/Gugg_coloured.jpg", "rb")
103 | local buf = f:read("*all")
104 | f:close()
105 | local im2 = vips.Image.new_from_buffer(buf)
106 |
107 | assert.are.equal(im:width(), im2:width())
108 | assert.are.equal(im:height(), im2:height())
109 | assert.are.equal(im:format(), im2:format())
110 | assert.are.equal(im:xres(), im2:xres())
111 | assert.are.equal(im:yres(), im2:yres())
112 | end)
113 |
114 | it("throws error when loading from unknown buffer", function()
115 | local buf = "GIF89a"
116 |
117 | assert.error_matches(function()
118 | vips.Image.new_from_buffer(buf)
119 | end, "unable to call VipsForeignLoadNsgifBuffer\ngifload_buffer: .+")
120 | end)
121 |
122 | it("can load a jpeg from a buffer, options in a table", function()
123 | local im = vips.Image.new_from_file("./spec/images/Gugg_coloured.jpg",
124 | { shrink = 2 })
125 | local f = io.open("./spec/images/Gugg_coloured.jpg", "rb")
126 | local buf = f:read("*all")
127 | f:close()
128 | local im2 = vips.Image.new_from_buffer(buf, "", { shrink = 2 })
129 |
130 | assert.are.equal(im:width(), im2:width())
131 | assert.are.equal(im:height(), im2:height())
132 | assert.are.equal(im:format(), im2:format())
133 | assert.are.equal(im:xres(), im2:xres())
134 | assert.are.equal(im:yres(), im2:yres())
135 | end)
136 |
137 | it("can load a jpeg from a buffer, options in a table", function()
138 | local im = vips.Image.new_from_file("./spec/images/Gugg_coloured.jpg",
139 | { shrink = 2 })
140 | local f = io.open("./spec/images/Gugg_coloured.jpg", "rb")
141 | local buf = f:read("*all")
142 | f:close()
143 | local im2 = vips.Image.new_from_buffer(buf, "shrink=2")
144 |
145 | assert.are.equal(im:width(), im2:width())
146 | assert.are.equal(im:height(), im2:height())
147 | assert.are.equal(im:format(), im2:format())
148 | assert.are.equal(im:xres(), im2:xres())
149 | assert.are.equal(im:yres(), im2:yres())
150 | end)
151 | end)
152 |
153 | describe("test image from memory", function()
154 | it("can make an image from a memory area", function()
155 | local width = 64
156 | local height = 32
157 | local size = width * height
158 | local data = ffi.new("unsigned char[?]", size)
159 |
160 | for y = 0, height - 1 do
161 | for x = 0, width - 1 do
162 | data[x + y * width] = x + y
163 | end
164 | end
165 |
166 | local im = vips.Image.new_from_memory(data,
167 | width, height, 1, "uchar")
168 |
169 | assert.are.equal(im:width(), width)
170 | assert.are.equal(im:height(), height)
171 | assert.are.equal(im:bands(), 1)
172 | assert.are.equal(im:format(), "uchar")
173 | assert.are.equal(im:avg(), 47)
174 | end)
175 |
176 | it("can make an image from a memory area (pointer)", function()
177 | local width = 64
178 | local height = 32
179 | local size = width * height
180 | local data = ffi.gc(ffi.cast("unsigned char*", ffi.C.malloc(size)), ffi.C.free)
181 |
182 | for y = 0, height - 1 do
183 | for x = 0, width - 1 do
184 | data[x + y * width] = x + y
185 | end
186 | end
187 |
188 | local im = vips.Image.new_from_memory_ptr(data,
189 | size, width, height, 1, "uchar")
190 |
191 | assert.are.equal(im:width(), width)
192 | assert.are.equal(im:height(), height)
193 | assert.are.equal(im:bands(), 1)
194 | assert.are.equal(im:format(), "uchar")
195 | assert.are.equal(im:avg(), 47)
196 | end)
197 | end)
198 |
199 | describe("test vips creators", function()
200 | it("can call vips_black()", function()
201 | local im = vips.Image.black(1, 1)
202 |
203 | assert.are.equal(im:width(), 1)
204 | assert.are.equal(im:height(), 1)
205 | assert.are.equal(im:bands(), 1)
206 | assert.are.equal(im:avg(), 0)
207 | end)
208 |
209 | it("can call operations with - in option names", function()
210 | local im = vips.Image.perlin(100, 100, { cell_size = 10 })
211 |
212 | assert.are.equal(im:width(), 100)
213 | assert.are.equal(im:height(), 100)
214 | assert.are.equal(im:bands(), 1)
215 | end)
216 | end)
217 |
218 | describe("test image from image", function()
219 | local im
220 |
221 | setup(function()
222 | im = vips.Image.new_from_file("./spec/images/Gugg_coloured.jpg")
223 | end)
224 |
225 | it("can make a one-band constant image", function()
226 | local im2 = im:new_from_image(12)
227 |
228 | assert.are.equal(im:width(), im2:width())
229 | assert.are.equal(im:height(), im2:height())
230 | assert.are.equal(im:format(), im2:format())
231 | assert.are.equal(im:interpretation(), im2:interpretation())
232 | assert.are.equal(im:xres(), im2:xres())
233 | assert.are.equal(im:yres(), im2:yres())
234 | assert.are.equal(im2:bands(), 1)
235 | assert.are.equal(im2:avg(), 12)
236 | end)
237 |
238 | it("can make a many-band constant image", function()
239 | local im2 = im:new_from_image({ 1, 2, 3, 4 })
240 |
241 | assert.are.equal(im:width(), im2:width())
242 | assert.are.equal(im:height(), im2:height())
243 | assert.are.equal(im:format(), im2:format())
244 | assert.are.equal(im:interpretation(), im2:interpretation())
245 | assert.are.equal(im:xres(), im2:xres())
246 | assert.are.equal(im:yres(), im2:yres())
247 | assert.are.equal(im2:bands(), 4)
248 | assert.are.equal(im2:avg(), 2.5)
249 | end)
250 | end)
251 | end)
252 |
--------------------------------------------------------------------------------
/src/vips/voperation.lua:
--------------------------------------------------------------------------------
1 | -- manage VipsOperation
2 | -- lookup and call operations
3 |
4 | local ffi = require "ffi"
5 |
6 | local verror = require "vips.verror"
7 | local version = require "vips.version"
8 | local log = require "vips.log"
9 | local gvalue = require "vips.gvalue"
10 | local vobject = require "vips.vobject"
11 | local Image = require "vips.Image"
12 |
13 | local type = type
14 | local error = error
15 | local pairs = pairs
16 | local unpack = unpack or table.unpack
17 | local tonumber = tonumber
18 | local str_gsub = string.gsub
19 |
20 | local vips_lib = ffi.load(ffi.os == "Windows" and "libvips-42.dll" or "vips")
21 |
22 | local REQUIRED = 1
23 | local CONSTRUCT = 2 -- luacheck: ignore
24 | local SET_ONCE = 4 -- luacheck: ignore
25 | local SET_ALWAYS = 8 -- luacheck: ignore
26 | local INPUT = 16
27 | local OUTPUT = 32
28 | local DEPRECATED = 64
29 | local MODIFY = 128
30 |
31 | local function isbitset(a, b)
32 | return ( (a - (a % b)) / b ) % 2 == 1
33 | end
34 |
35 | -- find the first image, and recurse
36 | local function find_first_image(array, length)
37 | length = length or #array
38 |
39 | for i = 1, length do
40 | if Image.is_Image(array[i]) then
41 | return array[i]
42 | elseif type(array[i]) == "table" then
43 | local result = find_first_image(array[i])
44 |
45 | if result then
46 | return result
47 | end
48 | end
49 | end
50 |
51 | return nil
52 | end
53 |
54 | local voperation = {}
55 |
56 | voperation.argumentmap_typeof = ffi.typeof("VipsArgumentMapFn")
57 | voperation.pstring_array_typeof = ffi.typeof("const char**[1]")
58 | voperation.pint_array_typeof = ffi.typeof("int*[1]")
59 | voperation.pint_typeof = ffi.typeof("int[1]")
60 |
61 | -- cast to a vobject ... this will create a new cdata object, but won't
62 | -- change any VipsObject reference counts, nor add a finalizer
63 | -- TODO: Could we use `self.parent_instance` here?
64 | voperation.vobject = function(self)
65 | return ffi.cast(vobject.typeof, self)
66 | end
67 |
68 | -- but for new() we can't do self:vobject():new() since that would
69 | -- attach the unref callback to the cdata object made by the vobject()
70 | -- cast, not to this voperation
71 | voperation.new = function(self)
72 | return vobject.new(self)
73 | end
74 |
75 | voperation.set = function(self, name, flags, match_image, value)
76 | local vob = self:vobject()
77 | local gtype = vob:get_typeof(name)
78 | local gtype_comp = gvalue.comparable_type(gtype)
79 |
80 | -- if the object wants an image and we have a constant, imageize it
81 | --
82 | -- if the object wants an image array, imageize any constants in the
83 | -- array
84 | if match_image then
85 | if gtype_comp == gvalue.image_type then
86 | value = match_image:imageize(value)
87 | elseif gtype_comp == gvalue.array_image_type then
88 | for i = 1, #value do
89 | value[i] = match_image:imageize(value[i])
90 | end
91 | end
92 | end
93 |
94 | -- MODIFY args need to be copied before they are set
95 | if isbitset(flags, MODIFY) then
96 | log.msg("copying MODIFY arg", name)
97 | -- make sure we have a unique copy
98 | value = value:copy():copy_memory()
99 | end
100 |
101 | return vob:set_type(name, value, gtype)
102 | end
103 |
104 | -- this is slow ... call as little as possible
105 | voperation.getargs = function(self)
106 | local names = {}
107 | local flags = {}
108 | local n_args = 0
109 |
110 | if version.at_least(8, 7) then
111 | local p_names = ffi.new(voperation.pstring_array_typeof)
112 | local p_flags = ffi.new(voperation.pint_array_typeof)
113 | local p_n_args = ffi.new(voperation.pint_typeof)
114 |
115 | vips_lib.vips_object_get_args(self, p_names, p_flags, p_n_args)
116 |
117 | p_names = p_names[0]
118 | p_flags = p_flags[0]
119 | n_args = p_n_args[0]
120 |
121 | -- C-array is numbered from zero
122 | for i = 0, n_args - 1 do
123 | names[i + 1] = str_gsub(ffi.string(p_names[i]), "-", "_")
124 | flags[i + 1] = p_flags[i]
125 | end
126 | else
127 | local cb = ffi.cast(voperation.argumentmap_typeof,
128 | function(_, pspec, argument_class, _, _, _)
129 | n_args = n_args + 1
130 |
131 | -- libvips uses "-" to separate parts of arg names, but we
132 | -- need "_" for lua
133 | names[n_args] = str_gsub(ffi.string(pspec.name), "-", "_")
134 | flags[n_args] = tonumber(argument_class.flags)
135 | end)
136 | vips_lib.vips_argument_map(self, cb, nil, nil)
137 | cb:free()
138 | end
139 |
140 | return names, flags, n_args
141 | end
142 |
143 | -- string_options is any optional args coded as a string, perhaps
144 | -- "[strip,tile=true]"
145 | voperation.call = function(name, string_options, ...)
146 | local call_args = { ... }
147 |
148 | local vop = vips_lib.vips_operation_new(name)
149 | if vop == ffi.NULL then
150 | error("no such operation\n" .. verror.get())
151 | end
152 | vop = vop:new()
153 |
154 | local names, flags, arguments_length = vop:getargs()
155 |
156 | -- cache the call args length
157 | local call_args_length = #call_args
158 |
159 | log.msg("calling operation:", name)
160 | log.msg("passed:")
161 | log.msg_r(call_args)
162 |
163 | -- make a thing to quickly get flags from an arg name
164 | local flags_from_name = {}
165 |
166 | -- count required input args
167 | local n_required = 0
168 | for i = 1, arguments_length do
169 | local flag = flags[i]
170 | flags_from_name[names[i]] = flag
171 |
172 | if isbitset(flag, INPUT) and
173 | isbitset(flag, REQUIRED) and
174 | not isbitset(flag, DEPRECATED) then
175 | n_required = n_required + 1
176 | end
177 | end
178 |
179 | -- so we should have been passed n_required, or n_required + 1 if
180 | -- there's a table of options at the end
181 | local last_arg
182 | if call_args_length == n_required then
183 | last_arg = nil
184 | elseif call_args_length == n_required + 1 then
185 | last_arg = call_args[#call_args]
186 | if type(last_arg) ~= "table" then
187 | error("unable to call " .. name .. ": " .. call_args_length ..
188 | " arguments given, " .. n_required ..
189 | ", but final argument is not a table")
190 | end
191 | else
192 | error("unable to call " .. name .. ": " .. call_args_length ..
193 | " arguments given, but " .. n_required .. " required")
194 | end
195 |
196 | -- the first image argument is the thing we expand constants to
197 | -- match ... look inside tables for images, since we may be passing
198 | -- an array of image as a single param
199 | local match_image = find_first_image(call_args, call_args_length)
200 |
201 | -- set any string options before any args so they can't be
202 | -- overridden
203 | if vips_lib.vips_object_set_from_string(vop:vobject(),
204 | string_options) ~= 0 then
205 | error("unable to call " .. name .. "\n" .. verror.get())
206 | end
207 |
208 | local n = 0
209 | for i = 1, arguments_length do
210 | local flag = flags[i]
211 |
212 | if isbitset(flag, INPUT) and
213 | isbitset(flag, REQUIRED) and
214 | not isbitset(flag, DEPRECATED) then
215 | n = n + 1
216 |
217 | if not vop:set(names[i], flag,
218 | match_image, call_args[n]) then
219 | error("unable to call " .. name .. "\n" .. verror.get())
220 | end
221 | end
222 | end
223 |
224 | if last_arg then
225 | for k, v in pairs(last_arg) do
226 | local flag = flags_from_name[k]
227 | if not flag then
228 | error("unable to call " .. name .. ": invalid flag '" ..
229 | tostring(k) .. "'")
230 | end
231 |
232 | if not vop:set(k, flag, match_image, v) then
233 | error("unable to call " .. name .. "\n" .. verror.get())
234 | end
235 | end
236 | end
237 |
238 | local vop2 = vips_lib.vips_cache_operation_build(vop)
239 | if vop2 == ffi.NULL then
240 | error("unable to call " .. name .. "\n" .. verror.get())
241 | end
242 | vop = vop2:new()
243 |
244 | local result = {}
245 | local vob = vop:vobject()
246 |
247 | -- fetch required output args, plus modified input images
248 | n = 1
249 | for i = 1, arguments_length do
250 | local flag = flags[i]
251 |
252 | if isbitset(flag, OUTPUT) and
253 | isbitset(flag, REQUIRED) and
254 | not isbitset(flag, DEPRECATED) then
255 | result[n] = vob:get(names[i])
256 | n = n + 1
257 | end
258 |
259 | -- MODIFY input args are returned .. this will get the copy we
260 | -- made above
261 | if isbitset(flag, INPUT) and
262 | isbitset(flag, MODIFY) then
263 | result[n] = vob:get(names[i])
264 | n = n + 1
265 | end
266 | end
267 |
268 | -- fetch optional output args
269 | for i = 1, arguments_length do
270 | local flag = flags[i]
271 |
272 | if isbitset(flag, OUTPUT) and
273 | not isbitset(flag, REQUIRED) and
274 | not isbitset(flag, DEPRECATED) then
275 | result[n] = vob:get(names[i])
276 | n = n + 1
277 | end
278 | end
279 |
280 | -- garbage collection during vips_object_unref_outputs leads to crashes on Lua 5.3
281 | collectgarbage("stop")
282 | vips_lib.vips_object_unref_outputs(vop)
283 | collectgarbage("restart")
284 |
285 | -- this strange if expression is because unpack
286 | -- has not yet been implemented in the JIT compiler
287 | -- of LuaJIT, see: http://wiki.luajit.org/NYI.
288 | if n == 1 then
289 | return nil
290 | elseif n == 2 then
291 | return result[1]
292 | else
293 | -- we could extend this if expression even more,
294 | -- but usually one item is returned.
295 | return unpack(result)
296 | end
297 | end
298 |
299 | return ffi.metatype("VipsOperation", {
300 | __index = voperation
301 | })
302 |
--------------------------------------------------------------------------------
/src/vips/gvalue.lua:
--------------------------------------------------------------------------------
1 | -- manipulate GValue objects from lua
2 | -- pull in gobject via the vips library
3 |
4 | local ffi = require "ffi"
5 |
6 | local verror = require "vips.verror"
7 | local version = require "vips.version"
8 | local Image = require "vips.Image"
9 |
10 | local type = type
11 | local error = error
12 |
13 | local vips_lib
14 | local gobject_lib
15 | local glib_lib
16 | if ffi.os == "Windows" then
17 | vips_lib = ffi.load("libvips-42.dll")
18 | gobject_lib = ffi.load("libgobject-2.0-0.dll")
19 | glib_lib = ffi.load("libglib-2.0-0.dll")
20 | else
21 | vips_lib = ffi.load("vips")
22 | gobject_lib = vips_lib
23 | glib_lib = vips_lib
24 | end
25 |
26 | if version.at_least(8, 6) then
27 | vips_lib.vips_blend_mode_get_type()
28 | end
29 | vips_lib.vips_band_format_get_type()
30 |
31 | local gvalue = {}
32 |
33 | -- make ffi constructors we can reuse
34 | gvalue.gv_typeof = ffi.typeof("GValue")
35 | gvalue.pgv_typeof = ffi.typeof("GValue[1]")
36 | gvalue.image_typeof = ffi.typeof("VipsImage*")
37 | gvalue.pint_typeof = ffi.typeof("int[?]")
38 | gvalue.int_arr_typeof = ffi.typeof("const int[?]")
39 | gvalue.double_arr_typeof = ffi.typeof("const double[?]")
40 | gvalue.psize_typeof = ffi.typeof("size_t[?]")
41 | gvalue.mem_typeof = ffi.typeof("unsigned char[?]")
42 | gvalue.interpolate_typeof = ffi.typeof("VipsInterpolate*")
43 | gvalue.connection_typeof = ffi.typeof("VipsConnection*")
44 | gvalue.source_typeof = ffi.typeof("VipsSource*")
45 | gvalue.target_typeof = ffi.typeof("VipsTarget*")
46 |
47 | -- look up some common gtypes at init for speed
48 | gvalue.gbool_type = gobject_lib.g_type_from_name("gboolean")
49 | gvalue.gint_type = gobject_lib.g_type_from_name("gint")
50 | gvalue.gdouble_type = gobject_lib.g_type_from_name("gdouble")
51 | gvalue.gstr_type = gobject_lib.g_type_from_name("gchararray")
52 | gvalue.genum_type = gobject_lib.g_type_from_name("GEnum")
53 | gvalue.gflags_type = gobject_lib.g_type_from_name("GFlags")
54 | gvalue.image_type = gobject_lib.g_type_from_name("VipsImage")
55 | gvalue.array_int_type = gobject_lib.g_type_from_name("VipsArrayInt")
56 | gvalue.array_double_type = gobject_lib.g_type_from_name("VipsArrayDouble")
57 | gvalue.array_image_type = gobject_lib.g_type_from_name("VipsArrayImage")
58 | gvalue.refstr_type = gobject_lib.g_type_from_name("VipsRefString")
59 | gvalue.blob_type = gobject_lib.g_type_from_name("VipsBlob")
60 | gvalue.band_format_type = gobject_lib.g_type_from_name("VipsBandFormat")
61 | gvalue.blend_mode_type = version.at_least(8, 6) and gobject_lib.g_type_from_name("VipsBlendMode") or 0
62 | gvalue.interpolate_type = gobject_lib.g_type_from_name("VipsInterpolate")
63 | gvalue.connection_type = gobject_lib.g_type_from_name("VipsConnection")
64 | gvalue.source_type = gobject_lib.g_type_from_name("VipsSource")
65 | gvalue.target_type = gobject_lib.g_type_from_name("VipsTarget")
66 |
67 | -- gvalue.*_type can be of type cdata or number depending on the OS and Lua version
68 | -- gtypes as returned by vips_lib can also be of type cdata or number
69 | -- cdata and number are not comparable with Standard Lua (using luaffi-tkl)
70 | gvalue.comparable_type = type(gvalue.gdouble_type) == "number" and
71 | function(gtype) return tonumber(gtype) end or
72 | function(gtype) return gtype end
73 | gvalue.to_enum = function(gtype, value)
74 | -- turn a string into an int, ready to be passed into libvips
75 | local enum_value
76 |
77 | if type(value) == "string" then
78 | enum_value = vips_lib.vips_enum_from_nick("lua-vips",
79 | gtype, value)
80 |
81 | if enum_value < 0 then
82 | error("no such enum " .. value .. "\n" .. verror.get())
83 | end
84 | else
85 | enum_value = value
86 | end
87 |
88 | return enum_value
89 | end
90 |
91 | gvalue.type_name = function(gtype)
92 | return ffi.string(gobject_lib.g_type_name(gtype))
93 | end
94 |
95 | gvalue.init = function(gv, gtype)
96 | gobject_lib.g_value_init(gv, gtype)
97 | end
98 |
99 | gvalue.set = function(gv, value)
100 | local gtype = gv.gtype
101 | local gtype_comp = gvalue.comparable_type(gtype)
102 | local fundamental = gobject_lib.g_type_fundamental(gtype)
103 |
104 | if gtype_comp == gvalue.gbool_type then
105 | gobject_lib.g_value_set_boolean(gv, value)
106 | elseif gtype_comp == gvalue.gint_type then
107 | gobject_lib.g_value_set_int(gv, value)
108 | elseif gtype_comp == gvalue.gdouble_type then
109 | gobject_lib.g_value_set_double(gv, value)
110 | elseif fundamental == gvalue.genum_type then
111 | gobject_lib.g_value_set_enum(gv, gvalue.to_enum(gtype, value))
112 | elseif fundamental == gvalue.gflags_type then
113 | gobject_lib.g_value_set_flags(gv, value)
114 | elseif gtype_comp == gvalue.gstr_type then
115 | gobject_lib.g_value_set_string(gv, value)
116 | elseif gtype_comp == gvalue.refstr_type then
117 | gobject_lib.vips_value_set_ref_string(gv, value)
118 | elseif gtype_comp == gvalue.image_type then
119 | gobject_lib.g_value_set_object(gv, value.vimage)
120 | elseif gtype_comp == gvalue.array_int_type then
121 | if type(value) == "number" then
122 | value = { value }
123 | end
124 |
125 | local n = #value
126 | local a = ffi.new(gvalue.int_arr_typeof, n, value)
127 |
128 | vips_lib.vips_value_set_array_int(gv, a, n)
129 | elseif gtype_comp == gvalue.array_double_type then
130 | if type(value) == "number" then
131 | value = { value }
132 | end
133 |
134 | local n = #value
135 | local a = ffi.new(gvalue.double_arr_typeof, n, value)
136 |
137 | vips_lib.vips_value_set_array_double(gv, a, n)
138 | elseif gtype_comp == gvalue.array_image_type then
139 | if Image.is_Image(value) then
140 | value = { value }
141 | end
142 |
143 | local n = #value
144 |
145 | vips_lib.vips_value_set_array_image(gv, n)
146 | local a = vips_lib.vips_value_get_array_image(gv, nil)
147 |
148 | for i = 0, n - 1 do
149 | a[i] = value[i + 1].vimage
150 | -- the gvalue needs a set of refs to own
151 | gobject_lib.g_object_ref(a[i])
152 | end
153 | elseif gtype_comp == gvalue.blob_type then
154 | -- we need to set the blob to a copy of the lua string that vips
155 | -- can own
156 | local n = #value
157 |
158 | local buf = glib_lib.g_malloc(n)
159 | ffi.copy(buf, value, n)
160 |
161 | if version.at_least(8, 6) then
162 | vips_lib.vips_value_set_blob_free(gv, buf, n)
163 | else
164 | vips_lib.vips_value_set_blob(gv, glib_lib.g_free, buf, n)
165 | end
166 | elseif gtype_comp == gvalue.interpolate_type then
167 | gobject_lib.g_value_set_object(gv, value)
168 | elseif gtype_comp == gvalue.connection_type then
169 | gobject_lib.g_value_set_object(gv, value)
170 | elseif gtype_comp == gvalue.source_type then
171 | gobject_lib.g_value_set_object(gv, value)
172 | elseif gtype_comp == gvalue.target_type then
173 | gobject_lib.g_value_set_object(gv, value)
174 | else
175 | error("unsupported gtype for set " .. gvalue.type_name(gtype))
176 | end
177 | end
178 |
179 | gvalue.get = function(gv)
180 | local gtype = gv.gtype
181 | local gtype_comp = gvalue.comparable_type(gtype)
182 | local fundamental = gobject_lib.g_type_fundamental(gtype)
183 |
184 | local result
185 |
186 | if gtype_comp == gvalue.gbool_type then
187 | result = gobject_lib.g_value_get_boolean(gv)
188 | elseif gtype_comp == gvalue.gint_type then
189 | result = gobject_lib.g_value_get_int(gv)
190 | elseif gtype_comp == gvalue.gdouble_type then
191 | result = gobject_lib.g_value_get_double(gv)
192 | elseif fundamental == gvalue.genum_type then
193 | local enum_value = gobject_lib.g_value_get_enum(gv)
194 |
195 | local cstr = vips_lib.vips_enum_nick(gtype, enum_value)
196 |
197 | if cstr == ffi.NULL then
198 | error("value not in enum")
199 | end
200 |
201 | result = ffi.string(cstr)
202 | elseif fundamental == gvalue.gflags_type then
203 | result = gobject_lib.g_value_get_flags(gv)
204 | elseif gtype_comp == gvalue.gstr_type then
205 | local cstr = gobject_lib.g_value_get_string(gv)
206 |
207 | if cstr ~= ffi.NULL then
208 | result = ffi.string(cstr)
209 | else
210 | result = nil
211 | end
212 | elseif gtype_comp == gvalue.refstr_type then
213 | local psize = ffi.new(gvalue.psize_typeof, 1)
214 |
215 | local cstr = vips_lib.vips_value_get_ref_string(gv, psize)
216 |
217 | result = ffi.string(cstr, tonumber(psize[0]))
218 | elseif gtype_comp == gvalue.image_type then
219 | -- g_value_get_object() will not add a ref ... that is
220 | -- held by the gvalue
221 | local vo = gobject_lib.g_value_get_object(gv)
222 | local vimage = ffi.cast(gvalue.image_typeof, vo)
223 |
224 | -- we want a ref that will last with the life of the vimage:
225 | -- this ref is matched by the unref that's attached to finalize
226 | -- by Image.new()
227 | gobject_lib.g_object_ref(vimage)
228 |
229 | result = Image.new(vimage)
230 | elseif gtype_comp == gvalue.array_int_type then
231 | local pint = ffi.new(gvalue.pint_typeof, 1)
232 |
233 | local array = vips_lib.vips_value_get_array_int(gv, pint)
234 | result = {}
235 | for i = 0, pint[0] - 1 do
236 | result[i + 1] = array[i]
237 | end
238 |
239 | elseif gtype_comp == gvalue.array_double_type then
240 | local pint = ffi.new(gvalue.pint_typeof, 1)
241 |
242 | local array = vips_lib.vips_value_get_array_double(gv, pint)
243 | result = {}
244 | for i = 0, pint[0] - 1 do
245 | result[i + 1] = array[i]
246 | end
247 | elseif gtype_comp == gvalue.array_image_type then
248 | local pint = ffi.new(gvalue.pint_typeof, 1)
249 |
250 | local array = vips_lib.vips_value_get_array_image(gv, pint)
251 | result = {}
252 | for i = 0, pint[0] - 1 do
253 | -- this will make a new cdata object
254 | local vimage = array[i]
255 |
256 | -- vips_value_get_array_image() adds a ref for each image in
257 | -- the array ... we must remember to drop them
258 | gobject_lib.g_object_ref(vimage)
259 |
260 | result[i + 1] = Image.new(vimage)
261 | end
262 | elseif gtype_comp == gvalue.blob_type then
263 | local psize = ffi.new(gvalue.psize_typeof, 1)
264 |
265 | local array = vips_lib.vips_value_get_blob(gv, psize)
266 |
267 | result = ffi.string(array, tonumber(psize[0]))
268 | elseif gtype_comp == gvalue.interpolate_type then
269 | local vo = gobject_lib.g_value_get_object(gv)
270 | result = ffi.cast(gvalue.interpolate_typeof, vo)
271 | elseif gtype_comp == gvalue.connection_type then
272 | local vo = gobject_lib.g_value_get_object(gv)
273 | result = ffi.cast(gvalue.connection_typeof, vo)
274 | elseif gtype_comp == gvalue.source_type then
275 | local vo = gobject_lib.g_value_get_object(gv)
276 | result = ffi.cast(gvalue.source_typeof, vo)
277 | elseif gtype_comp == gvalue.target_type then
278 | local vo = gobject_lib.g_value_get_object(gv)
279 | result = ffi.cast(gvalue.target_typeof, vo)
280 | else
281 | error("unsupported gtype for get " .. gvalue.type_name(gtype))
282 | end
283 |
284 | return result
285 | end
286 |
287 | return ffi.metatype("GValue", {
288 | __new = function(ct, pt)
289 | -- if pt equals to true you'll need to
290 | -- g_value_unset() yourself after calling it,
291 | -- it won't unset() automatically!
292 | return pt and ffi.new(gvalue.pgv_typeof) or ffi.new(ct)
293 | end,
294 |
295 | __gc = function(gv)
296 | gobject_lib.g_value_unset(gv)
297 | end,
298 |
299 | __index = gvalue
300 | })
301 |
--------------------------------------------------------------------------------
/src/vips/cdefs.lua:
--------------------------------------------------------------------------------
1 | -- all the C declarations for lua-vips
2 |
3 | local ffi = require "ffi"
4 |
5 | -- GType is an int the size of a pointer ... I don't think we can just use
6 | -- size_t, sadly
7 | if ffi.arch == "x64" or ffi.arch == "arm64" then
8 | ffi.cdef [[
9 | typedef uint64_t GType;
10 | ]]
11 | else
12 | ffi.cdef [[
13 | typedef uint32_t GType;
14 | ]]
15 | end
16 |
17 | ffi.cdef [[
18 | typedef struct _GValue {
19 | GType gtype;
20 | uint64_t data[2];
21 | } GValue;
22 |
23 | void *g_malloc (size_t size);
24 | void g_free (void *data);
25 |
26 | void g_object_ref (void *object);
27 | void g_object_unref (void *object);
28 |
29 | void g_value_init (GValue *value, GType gtype);
30 | void g_value_unset (GValue *value);
31 | const char *g_type_name (GType gtype);
32 | GType g_type_from_name (const char *name);
33 | GType g_type_fundamental (GType gtype);
34 |
35 | GType vips_blend_mode_get_type (void);
36 | GType vips_band_format_get_type (void);
37 |
38 | GType vips_type_find (const char *basename, const char *nickname);
39 |
40 | int vips_enum_from_nick (const char *domain,
41 | GType gtype, const char *str);
42 | const char *vips_enum_nick (GType gtype, int value);
43 |
44 | void g_value_set_boolean (GValue *value, int v_boolean);
45 | void g_value_set_int (GValue *value, int i);
46 | void g_value_set_double (GValue *value, double d);
47 | void g_value_set_enum (GValue *value, int e);
48 | void g_value_set_flags (GValue *value, unsigned int f);
49 | void g_value_set_string (GValue *value, const char *str);
50 | void vips_value_set_ref_string (GValue *value, const char *str);
51 | void g_value_set_object (GValue *value, void *object);
52 | void vips_value_set_array_double (GValue *value,
53 | const double *array, int n);
54 | void vips_value_set_array_int (GValue *value,
55 | const int *array, int n);
56 | void vips_value_set_array_image (GValue *value, int n);
57 | void vips_value_set_blob (GValue *value,
58 | void (*free_fn)(void *data), void *data, size_t length);
59 | void vips_value_set_blob_free (GValue *value,
60 | void *data, size_t length);
61 |
62 | int g_value_get_boolean (const GValue *value);
63 | int g_value_get_int (const GValue *value);
64 | double g_value_get_double (const GValue *value);
65 | int g_value_get_enum (const GValue *value);
66 | unsigned int g_value_get_flags (const GValue *value);
67 | const char *g_value_get_string (const GValue *value);
68 | const char *vips_value_get_ref_string (const GValue *value, size_t *length);
69 | void *g_value_get_object (const GValue *value);
70 | double *vips_value_get_array_double (const GValue *value, int *n);
71 | int *vips_value_get_array_int (const GValue *value, int *n);
72 | void *vips_value_get_blob (const GValue *value, size_t *length);
73 |
74 | typedef struct _GObject {
75 | void *g_type_instance;
76 | unsigned int ref_count;
77 | void *qdata;
78 | } GObject;
79 |
80 | typedef struct _VipsObject {
81 | GObject parent_instance;
82 | bool constructed;
83 | bool static_object;
84 | void *argument_table;
85 | char *nickname;
86 | char *description;
87 | bool preclose;
88 | bool close;
89 | bool postclose;
90 | size_t local_memory;
91 | } VipsObject;
92 |
93 | typedef struct _VipsObjectClass {
94 | // opaque
95 | } VipsObjectClass;
96 |
97 | typedef enum {
98 | G_PARAM_READABLE = 1,
99 | G_PARAM_WRITABLE = 2,
100 | G_PARAM_CONSTRUCT = 4,
101 | G_PARAM_CONSTRUCT_ONLY = 8,
102 | G_PARAM_LAX_VALIDATION = 16,
103 | G_PARAM_STATIC_NAME = 32,
104 | G_PARAM_PRIVATE = G_PARAM_STATIC_NAME,
105 | G_PARAM_STATIC_NICK = 64,
106 | G_PARAM_STATIC_BLURB = 128
107 | } GParamFlags;
108 |
109 | typedef struct _GParamSpec {
110 | void *g_type_instance;
111 |
112 | const char *name;
113 | GParamFlags flags;
114 | GType value_type;
115 | GType owner_type;
116 |
117 | // rest opaque
118 | } GParamSpec;
119 |
120 | typedef struct _VipsArgument {
121 | GParamSpec *pspec;
122 | } VipsArgument;
123 |
124 | typedef struct _VipsArgumentInstance {
125 | VipsArgument parent;
126 |
127 | // opaque
128 | } VipsArgumentInstance;
129 |
130 | typedef enum _VipsArgumentFlags {
131 | VIPS_ARGUMENT_NONE = 0,
132 | VIPS_ARGUMENT_REQUIRED = 1,
133 | VIPS_ARGUMENT_CONSTRUCT = 2,
134 | VIPS_ARGUMENT_SET_ONCE = 4,
135 | VIPS_ARGUMENT_SET_ALWAYS = 8,
136 | VIPS_ARGUMENT_INPUT = 16,
137 | VIPS_ARGUMENT_OUTPUT = 32,
138 | VIPS_ARGUMENT_DEPRECATED = 64,
139 | VIPS_ARGUMENT_MODIFY = 128
140 | } VipsArgumentFlags;
141 |
142 | typedef struct _VipsArgumentClass {
143 | VipsArgument parent;
144 |
145 | VipsObjectClass *object_class;
146 | VipsArgumentFlags flags;
147 | int priority;
148 | uint64_t offset;
149 | } VipsArgumentClass;
150 |
151 | int vips_object_get_argument (VipsObject *object,
152 | const char *name, GParamSpec **pspec,
153 | VipsArgumentClass **argument_class,
154 | VipsArgumentInstance **argument_instance);
155 |
156 | void g_object_set_property (VipsObject *object,
157 | const char *name, const GValue *value);
158 | void g_object_get_property (VipsObject *object,
159 | const char *name, GValue *value);
160 |
161 | void vips_object_print_all (void);
162 |
163 | int vips_object_set_from_string (VipsObject *object, const char *options);
164 |
165 | typedef struct _VipsImage {
166 | VipsObject parent_instance;
167 |
168 | // opaque
169 | } VipsImage;
170 |
171 | VipsImage *vips_image_get_gainmap(VipsImage *image);
172 |
173 | typedef struct _VipsConnection {
174 | VipsObject parent_instance;
175 |
176 | // opaque
177 | } VipsConnection;
178 |
179 | const char *vips_connection_filename (VipsConnection *connection);
180 | const char *vips_connection_nick (VipsConnection *connection);
181 |
182 | typedef struct _VipsSource {
183 | VipsConnection parent_instance;
184 |
185 | // opaque
186 | } VipsSource;
187 |
188 | typedef struct _VipsTarget {
189 | VipsConnection parent_instance;
190 |
191 | // opaque
192 | } VipsTarget;
193 |
194 | VipsSource *vips_source_new_from_descriptor (int descriptor);
195 | VipsSource *vips_source_new_from_file (const char *filename);
196 | // VipsSource *vips_source_new_from_blob (VipsBlob *blob);
197 | // VipsSource *vips_source_new_from_target (VipsTarget *target);
198 | VipsSource *vips_source_new_from_memory (const void *data, size_t size);
199 | // VipsSource *vips_source_new_from_options (const char *options);
200 | // void vips_source_minimise (VipsSource *source);
201 | // int vips_source_decode (VipsSource *source);
202 | // gint64 vips_source_read (VipsSource *source, void *data, size_t length);
203 | // gboolean vips_source_is_mappable (VipsSource *source);
204 | // gboolean vips_source_is_file (VipsSource *source);
205 | // const void *vips_source_map (VipsSource *source, size_t *length);
206 | // VipsBlob *vips_source_map_blob (VipsSource *source);
207 | // gint64 vips_source_seek (VipsSource *source, gint64 offset, int whence);
208 | // int vips_source_rewind (VipsSource *source);
209 | // gint64 vips_source_sniff_at_most (VipsSource *source, unsigned char **data, size_t length);
210 | // unsigned char *vips_source_sniff (VipsSource *source, size_t length);
211 | // gint64 vips_source_length (VipsSource *source);
212 | // VipsSourceCustom *vips_source_custom_new (void);
213 | // GInputStream *vips_g_input_stream_new_from_source (VipsSource *source);
214 | // VipsSourceGInputStream *vips_source_g_input_stream_new (GInputStream *stream);
215 |
216 | VipsTarget *vips_target_new_to_descriptor (int descriptor);
217 | VipsTarget *vips_target_new_to_file (const char *filename);
218 | VipsTarget *vips_target_new_to_memory (void);
219 | // VipsTarget *vips_target_new_temp (VipsTarget *target);
220 | // int vips_target_write (VipsTarget *target, const void *data, size_t length);
221 | // gint64 vips_target_read (VipsTarget *target, void *buffer, size_t length);
222 | // gint64 vips_target_seek (VipsTarget *target, gint64 offset, int whence);
223 | // int vips_target_end (VipsTarget *target);
224 | // unsigned char *vips_target_steal (VipsTarget *target, size_t *length);
225 | // char *vips_target_steal_text (VipsTarget *target);
226 | // int vips_target_putc (VipsTarget *target, int ch);
227 | // int vips_target_writes (VipsTarget *target, const char *str);
228 | // int vips_target_writef (VipsTarget *target, const char *fmt, ...);
229 | // int vips_target_write_amp (VipsTarget *target, const char *str);
230 | // VipsTargetCustom *vips_target_custom_new (void);
231 |
232 | const char *vips_foreign_find_load (const char *name);
233 | const char *vips_foreign_find_load_buffer (const void *data, size_t size);
234 | const char *vips_foreign_find_save (const char *name);
235 | const char *vips_foreign_find_save_buffer (const char *suffix);
236 | const char* vips_foreign_find_load_source (VipsSource *source);
237 | const char* vips_foreign_find_save_target (const char* suffix);
238 |
239 | VipsImage *vips_image_new_matrix_from_array (int width, int height,
240 | const double *array, int size);
241 |
242 | VipsImage *vips_image_new_from_memory (const void *data, size_t size,
243 | int width, int height, int bands, int format);
244 | unsigned char *vips_image_write_to_memory (VipsImage *image,
245 | size_t *size_out);
246 |
247 | VipsImage *vips_image_copy_memory (VipsImage *image);
248 |
249 | VipsImage **vips_value_get_array_image (const GValue *value, int *n);
250 |
251 | GType vips_image_get_typeof (const VipsImage *image, const char *name);
252 | int vips_image_get (const VipsImage *image, const char *name,
253 | GValue *value_copy);
254 | void vips_image_set (VipsImage *image, const char *name, GValue *value);
255 | int vips_image_remove (VipsImage *image, const char *name);
256 |
257 | int vips_image_hasalpha(VipsImage *image);
258 |
259 | char *vips_filename_get_filename (const char *vips_filename);
260 | char *vips_filename_get_options (const char *vips_filename);
261 |
262 | typedef struct _VipsOperation {
263 | VipsObject parent_instance;
264 |
265 | // opaque
266 | } VipsOperation;
267 |
268 | typedef struct _VipsInterpolate {
269 | VipsObject parent_instance;
270 |
271 | // opaque
272 | } VipsInterpolate;
273 |
274 | VipsInterpolate *vips_interpolate_new (const char *name);
275 |
276 | VipsOperation *vips_operation_new (const char *name);
277 |
278 | typedef void * (*VipsArgumentMapFn) (VipsOperation *object,
279 | GParamSpec *pspec,
280 | VipsArgumentClass *argument_class,
281 | VipsArgumentInstance *argument_instance,
282 | void *a, void *b);
283 |
284 | void *vips_argument_map (VipsOperation *object,
285 | VipsArgumentMapFn fn, void *a, void *b);
286 |
287 | void vips_object_get_args (VipsOperation *object,
288 | const char ***names, int **flags, int *n_args);
289 |
290 | VipsOperation *vips_cache_operation_build (VipsOperation *operation);
291 | void vips_object_unref_outputs (VipsOperation *operation);
292 |
293 | void vips_leak_set (int leak);
294 | void vips_cache_set_max (int max);
295 | void vips_cache_set_max (int max);
296 | int vips_cache_get_max (void);
297 | void vips_cache_set_max_mem (size_t max_mem);
298 | size_t vips_cache_get_max_mem (void);
299 | void vips_cache_set_max_files (int max_files);
300 | int vips_cache_get_max_files (void);
301 |
302 | void vips_concurrency_set (int concurrency);
303 | int vips_concurrency_get ();
304 |
305 | int vips_init (const char *argv0);
306 | int vips_version (int flag);
307 |
308 | const char *vips_error_buffer (void);
309 | void vips_error_clear (void);
310 |
311 | ]]
312 |
--------------------------------------------------------------------------------
/src/vips/Image_methods.lua:
--------------------------------------------------------------------------------
1 | -- an Image class with overloads
2 |
3 | local ffi = require "ffi"
4 |
5 | local verror = require "vips.verror"
6 | local version = require "vips.version"
7 | local gvalue = require "vips.gvalue"
8 | local vobject = require "vips.vobject"
9 | local voperation = require "vips.voperation"
10 | local Image = require "vips.Image"
11 |
12 | local type = type
13 | local error = error
14 | local pairs = pairs
15 | local ipairs = ipairs
16 | local unpack = unpack or table.unpack
17 | local rawget = rawget
18 | local setmetatable = setmetatable
19 | local getmetatable = getmetatable
20 |
21 | local vips_lib
22 | local gobject_lib
23 | local glib_lib
24 | if ffi.os == "Windows" then
25 | vips_lib = ffi.load("libvips-42.dll")
26 | gobject_lib = ffi.load("libgobject-2.0-0.dll")
27 | glib_lib = ffi.load("libglib-2.0-0.dll")
28 | else
29 | vips_lib = ffi.load("vips")
30 | gobject_lib = vips_lib
31 | glib_lib = vips_lib
32 | end
33 |
34 | -- test for rectangular array of something
35 | local function is_2D(table)
36 | if type(table) ~= "table" then
37 | return false
38 | end
39 |
40 | for i = 1, #table do
41 | if type(table[i]) ~= "table" then
42 | return false
43 | end
44 | if #table[i] ~= #table[1] then
45 | return false
46 | end
47 | end
48 |
49 | return true
50 | end
51 |
52 | local function map(fn, array)
53 | local new_array = {}
54 |
55 | for i, v in ipairs(array) do
56 | new_array[i] = fn(v)
57 | end
58 |
59 | return new_array
60 | end
61 |
62 | local function swap_Image_left(left, right)
63 | if Image.is_Image(left) then
64 | return left, right
65 | elseif Image.is_Image(right) then
66 | return right, left
67 | else
68 | error("must have one image argument")
69 | end
70 | end
71 |
72 | -- either a single number, or a table of numbers
73 | local function is_pixel(value)
74 | return type(value) == "number" or
75 | (type(value) == "table" and not Image.is_Image(value))
76 | end
77 |
78 | local function call_enum(image, other, base, operation)
79 | if type(other) == "number" then
80 | return image[base .. "_const"](image, operation, { other })
81 | elseif is_pixel(other) then
82 | return image[base .. "_const"](image, operation, other)
83 | else
84 | return image[base](image, other, operation)
85 | end
86 | end
87 |
88 | -- turn a string from libvips that must be g_free()d into a lua string
89 | local function to_string_copy(vips_string)
90 | local lua_string = ffi.string(vips_string)
91 | glib_lib.g_free(vips_string)
92 | return lua_string
93 | end
94 |
95 | -- class methods
96 |
97 | function Image.is_Image(thing)
98 | return type(thing) == "table" and getmetatable(thing) == Image.mt
99 | end
100 |
101 | function Image.imageize(self, value)
102 | -- careful! self can be nil if value is a 2D array
103 | if Image.is_Image(value) then
104 | return value
105 | elseif is_2D(value) then
106 | return Image.new_from_array(value)
107 | else
108 | return self:new_from_image(value)
109 | end
110 | end
111 |
112 | -- constructors
113 |
114 | -- we add an unref finalizer too! be careful
115 | function Image.new(vimage)
116 | local image = {}
117 |
118 | image.vimage = vobject.new(vimage)
119 |
120 | return setmetatable(image, Image.mt)
121 | end
122 |
123 | function Image.find_load(filename)
124 | local name = vips_lib.vips_foreign_find_load(filename)
125 | if name == ffi.NULL then
126 | return nil
127 | else
128 | return ffi.string(name)
129 | end
130 | end
131 |
132 | function Image.new_from_file(vips_filename, ...)
133 | local filename = to_string_copy(vips_lib.vips_filename_get_filename(vips_filename))
134 | local options = to_string_copy(vips_lib.vips_filename_get_options(vips_filename))
135 |
136 | local name = Image.find_load(filename)
137 | if name == nil then
138 | error(verror.get())
139 | end
140 |
141 | return voperation.call(name, options, filename, unpack { ... })
142 | end
143 |
144 | function Image.find_load_buffer(data)
145 | local name = vips_lib.vips_foreign_find_load_buffer(data, #data)
146 | if name == ffi.NULL then
147 | return nil
148 | else
149 | return ffi.string(name)
150 | end
151 | end
152 |
153 | function Image.new_from_buffer(data, options, ...)
154 | local name = Image.find_load_buffer(data)
155 | if name == nil then
156 | error(verror.get())
157 | end
158 |
159 | return voperation.call(name, options or "", data, unpack { ... })
160 | end
161 |
162 | function Image.new_from_memory_ptr(data, size, width, height, bands, format)
163 | local format_value = gvalue.to_enum(gvalue.band_format_type, format)
164 | local vimage = vips_lib.vips_image_new_from_memory(data, size,
165 | width, height, bands, format_value)
166 | if vimage == ffi.NULL then
167 | error(verror.get())
168 | end
169 | return Image.new(vimage)
170 | end
171 |
172 | function Image.new_from_memory(data, width, height, bands, format)
173 | local image = Image.new_from_memory_ptr(data, ffi.sizeof(data), width, height, bands, format)
174 | -- libvips is using the memory we passed in: save a pointer to the memory
175 | -- block to try to stop it being GCd
176 | image._data = data
177 | return image
178 | end
179 |
180 | function Image.new_from_array(array, scale, offset)
181 | if not is_2D(array) then
182 | array = { array }
183 | end
184 | local width = #array[1]
185 | local height = #array
186 |
187 | local n = width * height
188 | local arr = {}
189 | for y = 0, height - 1 do
190 | for x = 0, width - 1 do
191 | arr[x + y * width + 1] = array[y + 1][x + 1]
192 | end
193 | end
194 | local a = ffi.new(gvalue.double_arr_typeof, n, arr)
195 |
196 | local vimage = vips_lib.vips_image_new_matrix_from_array(width,
197 | height, a, n)
198 | local image = Image.new(vimage)
199 |
200 | image:set_type(gvalue.gdouble_type, "scale", scale or 1.0)
201 | image:set_type(gvalue.gdouble_type, "offset", offset or 0.0)
202 |
203 | return image
204 | end
205 |
206 | function Image.new_from_image(base_image, value)
207 | local pixel = (Image.black(1, 1) + value):cast(base_image:format())
208 | local image = pixel:embed(0, 0, base_image:width(), base_image:height(),
209 | { extend = "copy" })
210 | image = image:copy {
211 | interpretation = base_image:interpretation(),
212 | xres = base_image:xres(),
213 | yres = base_image:yres(),
214 | xoffset = base_image:xoffset(),
215 | yoffset = base_image:yoffset()
216 | }
217 |
218 | return image
219 | end
220 |
221 | function Image.new_from_source(source, options, ...)
222 | local name = vips_lib.vips_foreign_find_load_source(source.vconnection)
223 | if name == ffi.NULL then
224 | error("Unable to load from source")
225 | end
226 |
227 | return voperation.call(ffi.string(name), options, source.vconnection, unpack { ... })
228 | end
229 | -- overloads
230 |
231 | function Image.mt.__add(a, b)
232 | a, b = swap_Image_left(a, b)
233 |
234 | if type(b) == "number" then
235 | return a:linear({ 1 }, { b })
236 | elseif is_pixel(b) then
237 | return a:linear({ 1 }, b)
238 | else
239 | return a:add(b)
240 | end
241 | end
242 |
243 | function Image.mt.__sub(a, b)
244 | if Image.is_Image(a) then
245 | if type(b) == "number" then
246 | return a:linear({ 1 }, { -b })
247 | elseif is_pixel(b) then
248 | return a:linear({ 1 }, map(function(x)
249 | return -x
250 | end, b))
251 | else
252 | return a:subtract(b)
253 | end
254 | else
255 | -- therefore a is a constant and b is an image
256 | if type(a) == "number" then
257 | return (b * -1):linear({ 1 }, { a })
258 | else
259 | -- assume a is a pixel
260 | return (b * -1):linear({ 1 }, a)
261 | end
262 | end
263 | end
264 |
265 | function Image.mt.__mul(a, b)
266 | a, b = swap_Image_left(a, b)
267 |
268 | if type(b) == "number" then
269 | return a:linear({ b }, { 0 })
270 | elseif is_pixel(b) then
271 | return a:linear(b, { 0 })
272 | else
273 | return a:multiply(b)
274 | end
275 | end
276 |
277 | function Image.mt.__div(a, b)
278 | if Image.is_Image(a) then
279 | if type(b) == "number" then
280 | return a:linear({ 1 / b }, { 0 })
281 | elseif is_pixel(b) then
282 | return a:linear(map(function(x)
283 | return x ^ -1
284 | end, b), { 0 })
285 | else
286 | return a:divide(b)
287 | end
288 | else
289 | -- therefore a is a constant and b is an image
290 | if type(a) == "number" then
291 | return (b ^ -1):linear({ a }, { 0 })
292 | else
293 | -- assume a is a pixel
294 | return (b ^ -1):linear(a, { 0 })
295 | end
296 | end
297 | end
298 |
299 | function Image.mt.__mod(a, b)
300 | if not Image.is_Image(a) then
301 | error("constant % image not supported by libvips")
302 | end
303 |
304 | if type(b) == "number" then
305 | return a:remainder_const({ b })
306 | elseif is_pixel(b) then
307 | return a:remainder_const(b)
308 | else
309 | return a:remainder(b)
310 | end
311 | end
312 |
313 | function Image.mt.__unm(self)
314 | return self * -1
315 | end
316 |
317 | function Image.mt.__pow(a, b)
318 | if Image.is_Image(a) then
319 | return a:pow(b)
320 | else
321 | return b:wop(a)
322 | end
323 | end
324 |
325 | -- unfortunately, lua does not let you return non-bools from <, >, <=, >=, ==,
326 | -- ~=, so there's no point overloading these ... call :more(2) etc. instead
327 |
328 | function Image.mt:__tostring()
329 | local result = (self:filename() or "(nil)") .. ": " ..
330 | self:width() .. "x" .. self:height() .. " " ..
331 | self:format() .. ", " ..
332 | self:bands() .. " bands, " ..
333 | self:interpretation()
334 |
335 | if self:get_typeof("vips-loader") ~= 0 then
336 | result = result .. ", " .. self:get("vips-loader")
337 | end
338 |
339 | return result
340 | end
341 |
342 | function Image.mt:__call(x, y)
343 | -- getpoint() will return a table for a pixel
344 | return unpack(self:getpoint(x, y))
345 | end
346 |
347 | function Image.mt:__concat(other)
348 | return self:bandjoin(other)
349 | end
350 |
351 | -- instance methods
352 |
353 | local Image_method = {}
354 |
355 | function Image_method:vobject()
356 | -- TODO: Could we use `self.vimage.parent_instance` here?
357 | return ffi.cast(vobject.typeof, self.vimage)
358 | end
359 |
360 | -- handy to have as instance methods too
361 |
362 | function Image_method:imageize(value)
363 | return Image.imageize(self, value)
364 | end
365 |
366 | function Image_method:new_from_image(value)
367 | return Image.new_from_image(self, value)
368 | end
369 |
370 | function Image_method:copy_memory()
371 | local vimage = vips_lib.vips_image_copy_memory(self.vimage)
372 | if vimage == ffi.NULL then
373 | error(verror.get())
374 | end
375 | return Image.new(vimage)
376 | end
377 |
378 | -- writers
379 |
380 | function Image_method:write_to_file(vips_filename, ...)
381 | collectgarbage("stop")
382 | local filename = to_string_copy(vips_lib.vips_filename_get_filename(vips_filename))
383 | local options = to_string_copy(vips_lib.vips_filename_get_options(vips_filename))
384 | local name = vips_lib.vips_foreign_find_save(filename)
385 | collectgarbage("restart")
386 | if name == ffi.NULL then
387 | error(verror.get())
388 | end
389 | return voperation.call(ffi.string(name), options,
390 | self, filename, unpack { ... })
391 | end
392 |
393 | function Image_method:write_to_buffer(format_string, ...)
394 | collectgarbage("stop")
395 | local options = to_string_copy(vips_lib.vips_filename_get_options(format_string))
396 | local name = vips_lib.vips_foreign_find_save_buffer(format_string)
397 | collectgarbage("restart")
398 | if name == ffi.NULL then
399 | error(verror.get())
400 | end
401 |
402 | return voperation.call(ffi.string(name), options, self, unpack { ... })
403 | end
404 |
405 | function Image_method:write_to_memory()
406 | local psize = ffi.new(gvalue.psize_typeof, 1)
407 | local vips_memory = vips_lib.vips_image_write_to_memory(self.vimage, psize)
408 | local size = tonumber(psize[0])
409 |
410 | local lua_memory = ffi.new(gvalue.mem_typeof, size)
411 | ffi.copy(lua_memory, vips_memory, size)
412 | glib_lib.g_free(vips_memory)
413 |
414 | return lua_memory
415 | end
416 |
417 | function Image_method:write_to_memory_ptr()
418 | local psize = ffi.new(gvalue.psize_typeof, 1)
419 | local vips_memory = vips_lib.vips_image_write_to_memory(self.vimage, psize)
420 |
421 | return ffi.gc(vips_memory, glib_lib.g_free), tonumber(psize[0])
422 | end
423 |
424 | function Image_method:write_to_target(target, format_string, ...)
425 | collectgarbage("stop")
426 | local options = to_string_copy(vips_lib.vips_filename_get_options(format_string))
427 | local name = vips_lib.vips_foreign_find_save_target(format_string)
428 | collectgarbage("restart")
429 | if name == ffi.NULL then
430 | error(verror.get())
431 | end
432 |
433 | return voperation.call(ffi.string(name), options, self, target.vconnection, unpack { ... })
434 | end
435 | -- get/set metadata
436 |
437 | function Image_method:get_typeof(name)
438 | -- on libvips 8.4 and earlier, we need to fetch the type via
439 | -- our superclass get_typeof(), since vips_image_get_typeof() returned
440 | -- enum properties as ints
441 | if not version.at_least(8, 5) then
442 | local vob = self:vobject()
443 | local gtype = vob:get_typeof(name)
444 | if gtype ~= 0 then
445 | return vob:get_type(name, gtype)
446 | end
447 |
448 | -- we must clear the error buffer after vobject typeof fails
449 | verror.get()
450 | end
451 |
452 | return vips_lib.vips_image_get_typeof(self.vimage, name)
453 | end
454 |
455 | function Image_method:get(name)
456 | -- on libvips 8.4 and earlier, we need to fetch gobject properties via
457 | -- our superclass get(), since vips_image_get() returned enum properties
458 | -- as ints
459 | if not version.at_least(8, 5) then
460 | local vo = self:vobject()
461 | local gtype = vo:get_typeof(name)
462 | if gtype ~= 0 then
463 | return vo:get(name)
464 | end
465 |
466 | -- we must clear the error buffer after vobject typeof fails
467 | verror.get()
468 | end
469 |
470 | local pgv = gvalue(true)
471 |
472 | local result = vips_lib.vips_image_get(self.vimage, name, pgv)
473 | if result ~= 0 then
474 | error("unable to get " .. name)
475 | end
476 |
477 | result = pgv[0]:get()
478 | gobject_lib.g_value_unset(pgv[0])
479 |
480 | return result
481 | end
482 |
483 | function Image_method:set_type(gtype, name, value)
484 | local pgv = gvalue(true)
485 | pgv[0]:init(gtype)
486 | pgv[0]:set(value)
487 | vips_lib.vips_image_set(self.vimage, name, pgv)
488 | gobject_lib.g_value_unset(pgv[0])
489 | end
490 |
491 | function Image_method:set(name, value)
492 | local gtype = self:get_typeof(name)
493 | self:set_type(gtype, name, value)
494 | end
495 |
496 | function Image_method:remove(name)
497 | return vips_lib.vips_image_remove(self.vimage, name) ~= 0
498 | end
499 |
500 | function Image_method:get_gainmap()
501 | collectgarbage("stop")
502 | local vimage = vips_lib.vips_image_get_gainmap(self.vimage)
503 | collectgarbage("restart")
504 | if vimage == ffi.NULL then
505 | return nil
506 | end
507 |
508 | return Image.new(vimage)
509 | end
510 | -- standard header fields
511 |
512 | function Image_method:width()
513 | return self:get("width")
514 | end
515 |
516 | function Image_method:height()
517 | return self:get("height")
518 | end
519 |
520 | function Image_method:size()
521 | return self:width(), self:height()
522 | end
523 |
524 | function Image_method:bands()
525 | return self:get("bands")
526 | end
527 |
528 | function Image_method:format()
529 | return self:get("format")
530 | end
531 |
532 | function Image_method:interpretation()
533 | return self:get("interpretation")
534 | end
535 |
536 | function Image_method:xres()
537 | return self:get("xres")
538 | end
539 |
540 | function Image_method:yres()
541 | return self:get("yres")
542 | end
543 |
544 | function Image_method:xoffset()
545 | return self:get("xoffset")
546 | end
547 |
548 | function Image_method:yoffset()
549 | return self:get("yoffset")
550 | end
551 |
552 | function Image_method:filename()
553 | return self:get("filename")
554 | end
555 |
556 | -- many-image input operations
557 | --
558 | -- these don't wrap well automatically, since self is held separately
559 |
560 | function Image_method:bandjoin(other, options)
561 | -- allow a single untable arg as well
562 | if type(other) == "number" or Image.is_Image(other) then
563 | other = { other }
564 | end
565 |
566 | -- if other is all constants, we can use bandjoin_const
567 | local all_constant = true
568 | for i = 1, #other do
569 | if type(other[i]) ~= "number" then
570 | all_constant = false
571 | break
572 | end
573 | end
574 |
575 | if all_constant then
576 | return voperation.call("bandjoin_const", "", self, other, options)
577 | else
578 | return voperation.call("bandjoin", "", { self, unpack(other) }, options)
579 | end
580 | end
581 |
582 | function Image_method:bandrank(other, options)
583 | if type(other) ~= "table" then
584 | other = { other }
585 | end
586 |
587 | return voperation.call("bandrank", "", { self, unpack(other) }, options)
588 | end
589 |
590 | function Image_method:composite(other, mode, options)
591 | -- allow a single untable arg as well
592 | if type(other) == "number" or Image.is_Image(other) then
593 | other = { other }
594 | end
595 | if type(mode) ~= "table" then
596 | mode = { mode }
597 | end
598 |
599 | -- need to map str -> int by hand, since the mode arg is actually
600 | -- arrayint
601 | for i = 1, #mode do
602 | mode[i] = gvalue.to_enum(gvalue.blend_mode_type, mode[i])
603 | end
604 |
605 | return voperation.call("composite", "", { self, unpack(other) }, mode, options)
606 | end
607 |
608 | -- convenience functions
609 |
610 | function Image_method:hasalpha()
611 | return vips_lib.vips_image_hasalpha(self.vimage) ~= 0
612 | end
613 |
614 | -- addalpha was made a VipsOperation in vips 8.16; earlier versions need this polyfill
615 | if not version.at_least(8, 16) then
616 | function Image_method:addalpha()
617 | local max_alpha
618 | if self:interpretation() == "rgb16" or self:interpretation() == "grey16" then
619 | max_alpha = 65535
620 | elseif self:interpretation() == "scrgb" then
621 | max_alpha = 1.0
622 | else
623 | max_alpha = 255
624 | end
625 |
626 | return self:bandjoin(max_alpha)
627 | end
628 | end
629 |
630 | function Image_method:bandsplit()
631 | local result
632 |
633 | result = {}
634 | for i = 0, self:bands() - 1 do
635 | result[i + 1] = self:extract_band(i)
636 | end
637 |
638 | return result
639 | end
640 |
641 | -- special behaviour wrappers
642 |
643 | function Image_method:ifthenelse(then_value, else_value, options)
644 | -- We need different imageize rules for this. We need then_value
645 | -- and else_value to match each other first, and only if they
646 | -- are both constants do we match to self.
647 |
648 | local match_image
649 |
650 | for _, v in pairs({ then_value, else_value, self }) do
651 | if Image.is_Image(v) then
652 | match_image = v
653 | break
654 | end
655 | end
656 |
657 | if not Image.is_Image(then_value) then
658 | then_value = match_image:imageize(then_value)
659 | end
660 |
661 | if not Image.is_Image(else_value) then
662 | else_value = match_image:imageize(else_value)
663 | end
664 |
665 | return voperation.call("ifthenelse", "", self, then_value, else_value, options)
666 | end
667 |
668 | -- enum expansions
669 |
670 | function Image_method:pow(other)
671 | return call_enum(self, other, "math2", "pow")
672 | end
673 |
674 | function Image_method:wop(other)
675 | return call_enum(self, other, "math2", "wop")
676 | end
677 |
678 | function Image_method:lshift(other)
679 | return call_enum(self, other, "boolean", "lshift")
680 | end
681 |
682 | function Image_method:rshift(other)
683 | return call_enum(self, other, "boolean", "rshift")
684 | end
685 |
686 | function Image_method:andimage(other)
687 | return call_enum(self, other, "boolean", "and")
688 | end
689 |
690 | function Image_method:orimage(other)
691 | return call_enum(self, other, "boolean", "or")
692 | end
693 |
694 | function Image_method:eorimage(other)
695 | return call_enum(self, other, "boolean", "eor")
696 | end
697 |
698 | function Image_method:less(other)
699 | return call_enum(self, other, "relational", "less")
700 | end
701 |
702 | function Image_method:lesseq(other)
703 | return call_enum(self, other, "relational", "lesseq")
704 | end
705 |
706 | function Image_method:more(other)
707 | return call_enum(self, other, "relational", "more")
708 | end
709 |
710 | function Image_method:moreeq(other)
711 | return call_enum(self, other, "relational", "moreeq")
712 | end
713 |
714 | function Image_method:equal(other)
715 | return call_enum(self, other, "relational", "equal")
716 | end
717 |
718 | function Image_method:noteq(other)
719 | return call_enum(self, other, "relational", "noteq")
720 | end
721 |
722 | function Image_method:floor()
723 | return self:round("floor")
724 | end
725 |
726 | function Image_method:ceil()
727 | return self:round("ceil")
728 | end
729 |
730 | function Image_method:rint()
731 | return self:round("rint")
732 | end
733 |
734 | function Image_method:bandand()
735 | return self:bandbool("and")
736 | end
737 |
738 | function Image_method:bandor()
739 | return self:bandbool("or")
740 | end
741 |
742 | function Image_method:bandeor()
743 | return self:bandbool("eor")
744 | end
745 |
746 | function Image_method:real()
747 | return self:complexget("real")
748 | end
749 |
750 | function Image_method:imag()
751 | return self:complexget("imag")
752 | end
753 |
754 | function Image_method:polar()
755 | return self:complex("polar")
756 | end
757 |
758 | function Image_method:rect()
759 | return self:complex("rect")
760 | end
761 |
762 | function Image_method:conj()
763 | return self:complex("conj")
764 | end
765 |
766 | function Image_method:sin()
767 | return self:math("sin")
768 | end
769 |
770 | function Image_method:cos()
771 | return self:math("cos")
772 | end
773 |
774 | function Image_method:tan()
775 | return self:math("tan")
776 | end
777 |
778 | function Image_method:asin()
779 | return self:math("asin")
780 | end
781 |
782 | function Image_method:acos()
783 | return self:math("acos")
784 | end
785 |
786 | function Image_method:atan()
787 | return self:math("atan")
788 | end
789 |
790 | function Image_method:exp()
791 | return self:math("exp")
792 | end
793 |
794 | function Image_method:exp10()
795 | return self:math("exp10")
796 | end
797 |
798 | function Image_method:log()
799 | return self:math("log")
800 | end
801 |
802 | function Image_method:log10()
803 | return self:math("log10")
804 | end
805 |
806 | function Image_method:erode(mask)
807 | return self:morph(mask, "erode")
808 | end
809 |
810 | function Image_method:dilate(mask)
811 | return self:morph(mask, "dilate")
812 | end
813 |
814 | function Image_method:fliphor()
815 | return self:flip("horizontal")
816 | end
817 |
818 | function Image_method:flipver()
819 | return self:flip("vertical")
820 | end
821 |
822 | function Image_method:rot90()
823 | return self:rot("d90")
824 | end
825 |
826 | function Image_method:rot180()
827 | return self:rot("d180")
828 | end
829 |
830 | function Image_method:rot270()
831 | return self:rot("d270")
832 | end
833 |
834 | -- this is for undefined class / instance methods, like Image.text or image:avg
835 | local fall_back = function(name)
836 | return function(...)
837 | return voperation.call(name, "", unpack { ... })
838 | end
839 | end
840 |
841 | function Image.mt.__index(_, name)
842 | -- try to get instance method otherwise fallback to voperation
843 | return rawget(Image_method, name) or fall_back(name)
844 | end
845 |
846 | return setmetatable(Image, {
847 | __index = function(_, name)
848 | -- undefined class methods
849 | return fall_back(name)
850 | end
851 | })
852 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # lua-vips
2 |
3 | [](https://github.com/libvips/lua-vips/actions)
4 |
5 | This is a Lua binding for the [libvips image processing
6 | library](http://libvips.github.io/libvips). `libvips`
7 | is a [fast image processing library with low memory needs](https://github.com/jcupitt/lua-vips-bench).
8 | `lua-vips` uses ffi and needs either
9 | - luajit >= 2.0 or
10 | - standard Lua (5.1 up to 5.4) combined with the [`luaffi-tkl`](https://luarocks.org/modules/sudheerhebbale/luaffi-tkl) Lua package.
11 |
12 | On the x64 architecture `lua-vips` is continuously tested
13 | - on Linux and MacOS with Lua 5.1, 5.2, 5.3, 5.4 and openresty-luajit
14 | - on Windows using [MSYS2 MinGW-w64](https://www.msys2.org/) with Lua 5.3, 5.4 and luajit
15 |
16 | `lua-vips` should work on arm64 (recently tested on a Pinephone Pro using PostmarketOS) and possibly x86 (currently untested)
17 | as well.
18 |
19 | The libvips documentation includes a handy searchable table of [every operation in
20 | libvips](http://libvips.github.io/libvips/API/current/func-list.html). This
21 | is a good place to check if it supports some feature you need. Read on to
22 | see how to call libvips operations.
23 |
24 | # Example
25 |
26 | [Install the libvips shared
27 | library](https://libvips.github.io/libvips/install.html), then install this rock with:
28 |
29 | ```shell
30 | luarocks install lua-vips
31 | ```
32 |
33 | When used with LuaJIT please first exhibit luaffi-tkl as provided by the VM via:
34 | ```shell
35 | luarocks config --lua-version=5.1 rocks_provided.luaffi-tkl 2.1-1
36 | ```
37 |
38 | Example:
39 |
40 | ```lua
41 | local vips = require "vips"
42 |
43 | -- fast thumbnail generator
44 | local image = vips.Image.thumbnail("somefile.jpg", 128)
45 | image:write_to_file("tiny.jpg")
46 |
47 | -- make a new image with some text rendered on it
48 | image = vips.Image.text("Hello World!", {dpi = 300})
49 |
50 | -- call a method
51 | image = image:invert()
52 |
53 | -- use the `..` operator to join images bandwise
54 | image = image .. image .. image
55 |
56 | -- add a constant
57 | image = image + 12
58 | -- add a different value to each band
59 | image = image + { 1, 2, 3 }
60 | -- add two images
61 | image = image + image
62 |
63 | -- split bands up again
64 | b1, b2, b3 = image:bandsplit()
65 |
66 | -- read a pixel from coordinate (10, 20)
67 | r, g, b = image(10, 20)
68 |
69 | -- make all pixels less than 128 bright blue
70 | -- :less(128) makes an 8-bit image where each band is 255 (true) if that
71 | -- value is less than 128, and 0 (false) if it's >= 128 ... you can use
72 | --- images or {1,2,3} constants as well as simple values
73 | -- :bandand() joins all image bands together with bitwise AND, so you get a
74 | -- one-band image which is true where all bands are true
75 | -- condition:ifthenelse(then, else) takes a condition image and uses true or
76 | -- false values to pick pixels from the then or else images ... then and
77 | -- else can be constants or images
78 | image = image:less(128):bandand():ifthenelse({ 0, 0, 255 }, image)
79 |
80 | -- go to Yxy colourspace
81 | image = image:colourspace("yxy")
82 |
83 | -- pass options to a save operation
84 | image:write_to_file("x.png", { compression = 9 })
85 | ```
86 |
87 | # How it works
88 |
89 | libvips has quite a bit of introspection machinery built in.
90 |
91 | When you call something like `image:hough_circle{ scale = 4 }`, the `__index`
92 | method on the `lua-vips` image class opens libvips with ffi and searches
93 | for an operation called `hough_circle`. It discovers what arguments the
94 | operation takes, checks you supplied the correct arguments, and transforms
95 | them into the form that libvips needs. It executes the operator, then pulls
96 | out all the results and returns them as a Lua table.
97 |
98 | This means that, although `lua-vips` supports almost 300 operators, the
99 | binding itself is small, should be simple to maintain, and should always
100 | be up to date.
101 |
102 | # Getting more help
103 |
104 | The libvips website has a handy table of [all the libvips
105 | operators](http://libvips.github.io/libvips/API/current/func-list.html). Each
106 | one links to the main API docs so you can see what you need to pass to it.
107 |
108 | A simple way to see the arguments for an operation is to try running it
109 | from the command-line. For example:
110 |
111 | ```bash
112 | $ vips embed
113 | embed an image in a larger image
114 | usage:
115 | embed in out x y width height
116 | where:
117 | in - Input image, input VipsImage
118 | out - Output image, output VipsImage
119 | x - Left edge of input in output, input gint
120 | default: 0
121 | min: -1000000000, max: 1000000000
122 | y - Top edge of input in output, input gint
123 | default: 0
124 | min: -1000000000, max: 1000000000
125 | width - Image width in pixels, input gint
126 | default: 1
127 | min: 1, max: 1000000000
128 | height - Image height in pixels, input gint
129 | default: 1
130 | min: 1, max: 1000000000
131 | optional arguments:
132 | extend - How to generate the extra pixels, input VipsExtend
133 | default: black
134 | allowed: black, copy, repeat, mirror, white, background
135 | background - Color for background pixels, input VipsArrayDouble
136 | operation flags: sequential
137 | ```
138 |
139 | So you can call `embed` like this:
140 |
141 | ```lua
142 | local image = image:embed(100, 100, image:width() + 200, image:height() + 200,
143 | { extend = "mirror" })
144 | ```
145 |
146 | To add a 100 pixel mirror edge around an image.
147 |
148 | # Features
149 |
150 | This section runs through the main features of the binding.
151 |
152 | To load the binding use:
153 |
154 | ```lua
155 | local vips = require "vips"
156 | ```
157 |
158 | ## Make images
159 |
160 | You can make images from files or from buffers (Lua strings), you can wrap
161 | a vips image around an ffi array, or you can use one of the libvips create
162 | operators to make an image for you.
163 |
164 | ### `image = vips.Image.new_from_file(filename [, options])`
165 |
166 | Opens the file and returns an image. You can pass a set of options in a final
167 | table argument, for example:
168 |
169 | ```lua
170 | local image = vips.Image.new_from_file("somefile.jpg",
171 | { access = "sequential" })
172 | ```
173 |
174 | Some options are specific to some file types, for example, `shrink`, meaning
175 | shrink by an integer factor during load, only applies to images loaded via
176 | libjpeg.
177 |
178 | You can embed options in filenames using the standard libvips syntax. For
179 | example, these are equivalent:
180 |
181 | ```lua
182 | local image = vips.Image.new_from_file("somefile.jpg", { shrink = 2 })
183 | local image = vips.Image.new_from_file("somefile.jpg[shrink=2]")
184 | ```
185 |
186 | You can call specific file format loaders directly, for example:
187 |
188 | ```lua
189 | local image = vips.Image.jpegload("somefile.jpg", { shrink = 4 })
190 | ```
191 |
192 | The [loader section in the API
193 | docs](http://libvips.github.io/libvips/API/current/VipsForeignSave.html) lists
194 | all loaders and their options.
195 |
196 | ### `image = vips.Image.new_from_buffer(string [, string_options, options])`
197 |
198 | The string argument should contain an image file in some container format, such
199 | as JPEG. You can supply options, just as with `new_from_file`. These are
200 | equivalent:
201 |
202 | ```lua
203 | local image = vips.Image.new_from_buffer(string, "", { shrink = 2 })
204 | local image = vips.Image.new_from_buffer(string, "shrink=2")
205 | ```
206 |
207 | Use (for example) `vips.Image.jpegload_buffer` to call a loader directly.
208 |
209 | ### `image = vips.Image.new_from_memory(data, width, height, bands, format)`
210 |
211 | This wraps a libvips image around a FFI memory array. The memory array should be
212 | formatted as a C-style array. Images are always band-interleaved, so an RGB
213 | image three pixels across and two pixels down, for example, is laid out as:
214 |
215 | ```
216 | RGBRGBRGB
217 | RGBRGBRGB
218 | ```
219 |
220 | Example:
221 |
222 | ```lua
223 | local width = 64
224 | local height = 32
225 | local data = ffi.new("unsigned char[?]", width * height)
226 | local im = vips.Image.new_from_memory(data, width, height, 1, "uchar")
227 | ```
228 |
229 | The returned image is using a pointer to the `data` area, but the Lua/LuaJIT interpreter won't always know this. You should keep a reference to `data` alive for as long as you are using any downstream images, or you'll get a crash.
230 |
231 | ### `image = vips.Image.new_from_memory_ptr(data, size, width, height, bands, format)`
232 |
233 | Same as `new_from_memory`, but for any kind of data pointer (non-FFI allocated) by specifying the length of the data in bytes. The pointed data must be valid for the lifespan of the image and any downstream images.
234 |
235 | A string can be used as the data pointer thanks to FFI semantics.
236 |
237 | ### `image = vips.Image.new_from_image(image, pixel)`
238 |
239 | Makes a new image with the size, format, and resolution of `image`, but with
240 | each pixel having the value `pixel`. For example:
241 |
242 | ```lua
243 | local new_image = vips.Image.new_from_image(image, 12)
244 | ```
245 |
246 | Will make a new image with one band where every pixel has the value 12. You can
247 | call it as a member function. `pixel` can be a table to make a many-band image,
248 | for example:
249 |
250 | ```lua
251 | local new_image = image:new_from_image{ 1, 2, 3 }
252 | ```
253 |
254 | Will make a new three-band image, where all the red pixels have the value 1,
255 | greens are 2 and blues are 3.
256 |
257 | ### `image = vips.Image.new_from_array(array [, scale [, offset]])`
258 |
259 | Makes a new image from a Lua table. For example:
260 |
261 | ```lua
262 | local image = vips.Image.new_from_array{ 1, 2, 3 }
263 | ```
264 |
265 | Makes a one-band image, three pixels across and one high. Use nested tables for
266 | 2D images. You can set a scale and offset with two extra number parameters --
267 | handy for integer convolution masks.
268 |
269 | ```lua
270 | local mask = vips.Image.new_from_array(
271 | {{-1, -1, -1},
272 | {-1, 16, -1},
273 | {-1, -1, -1}}, 8)
274 | local image = image:conv(mask, { precision = "integer" })
275 | ```
276 |
277 | ### `image = vips.Image.copy_memory(self)`
278 |
279 | The image is rendered to a large memory buffer, and a new image is returned
280 | which represents the memory area.
281 |
282 | This is handy for breaking a pipeline.
283 |
284 | ### `image = vips.Image.black(width, height)`
285 |
286 | Makes a new one band, 8 bit, black image. You can call any of the libvips image
287 | creation operators in this way, for example:
288 |
289 | ```lua
290 | local noise = vips.Image.perlin(256, 256, { cell_size = 128 })
291 | ```
292 |
293 | See:
294 |
295 | [http://libvips.github.io/libvips/API/current/libvips-create.html](http://libvips.github.io/libvips/API/current/libvips-create.html)
296 |
297 | ## Get and set image metadata
298 |
299 | You can read and write aribitrary image metadata.
300 |
301 | ### `number = vips.Image.get_typeof(image, field_name)`
302 |
303 | This returns the GType for a field, or 0 if the field does not exist.
304 | `vips.gvalue` has a set of GTypes you can check against.
305 |
306 | ### `mixed = vips.Image.get(image, field_name)`
307 |
308 | This reads any named piece of metadata from the image, for example:
309 |
310 | ```lua
311 | local version = image:get("exif-ifd2-ExifVersion")
312 | ```
313 |
314 | The item is converted to some Lua type in the obvious way. There are convenient
315 | shortcuts for many of the standard fields, so these are equivalent:
316 |
317 | ```lua
318 | local width = image:get("width")
319 | local width = image:width()
320 | ```
321 |
322 | If the field does not exist, `lua-vips` will throw an error. Use `get_typeof`
323 | to check for the existence of a field.
324 |
325 | ### `vips.Image.set_type(image, gtype, field_name, value)`
326 |
327 | This creates a new metadata item of the specified type, name and value.
328 |
329 | ### `vips.Image.set(image, field_name, value)`
330 |
331 | This changes the value of an existing field, but will not change its type.
332 |
333 | You can't use `set()` to change core fields such as like `width` or
334 | `interpretation`. Use `copy()` instead.
335 |
336 | Image references will be shared by the operation cache, so modifying an image
337 | can change an image somewhere else in your program. Before changing an image,
338 | you must make sure you own a private copy of an image with `copy`.
339 |
340 | ```lua
341 | local new_image = image:copy()
342 | new_image:set("orientation", 7)
343 | ```
344 |
345 | ### `boolean = vips.Image.remove(image, field_name)`
346 |
347 | This will remove a piece of metadata. It returns `true` if an item was
348 | successfully removed, `false` otherwise.
349 |
350 | As with `set`, you must use copy before removing a metadata item.
351 |
352 | ## Call any libvips operation
353 |
354 | You can call any libvips operation as a member function, for example
355 | `hough_circle`, the circular Hough transform:
356 |
357 | [http://libvips.github.io/libvips/API/current/libvips-arithmetic.html#vips-hough-circle](http://libvips.github.io/libvips/API/current/libvips-arithmetic.html#vips-hough-circle)
358 |
359 | Can be called from Lua like this:
360 |
361 | ```lua
362 | local image2 = image:hough_circle{ scale = 2, max_radius = 50 }
363 | ```
364 |
365 | The rules are:
366 |
367 | 1. `self` is used to set the first required input image argument.
368 |
369 | 2. If you supply one more argument than the number of required arguments,
370 | and the final argument you supply is a table,
371 | that extra table is used to set any optional input arguments.
372 |
373 | 3. If you supply a constant (a number, or a table of numbers) and libvips
374 | wants an image, your constant is automatically turned into an image using
375 | the first input image you supplied as a guide.
376 |
377 | 4. For enums, you can supply a number or a string. The string is an enum member
378 | nickname (the part after the final underscore).
379 |
380 | 5. `MODIFY` arguments, for example the image you pass to `draw_circle`, are
381 | copied to memory before being set, and the new image is returned as one of
382 | the results.
383 |
384 | 6. Operation results are returned as an unpacked array in the order: all
385 | required output args, then all optional output args, then all deprecated
386 | output args.
387 |
388 | You can write (for example):
389 |
390 | ```lua
391 | max_value = image:max()
392 | ```
393 |
394 | To get the maximum value from an image. If you look at [the `max`
395 | operator](http://libvips.github.io/libvips/API/current/libvips-arithmetic.html#vips-max),
396 | it can actually return a lot more than this. You can write:
397 |
398 | ```lua
399 | max_value, x, y = image:max()
400 | ```
401 |
402 | To get the position of the maximum, or:
403 |
404 | ```lua
405 | max_value, x, y, maxes = image:max{ size = 10 }
406 | ```
407 |
408 | and `maxes` will be an array of the top 10 maximum values in order.
409 |
410 | ## Operator overloads
411 |
412 | The Lua operators are overloaded in the obvious way, so you can write (for
413 | example):
414 |
415 | ```lua
416 | image = (image * 2 + 13) % 4
417 | ```
418 |
419 | and the appropriate vips operations will be called. You can mix images, number
420 | constants, and array constants freely.
421 |
422 | The relational operators are not overloaded, unfortunately; Lua does not
423 | permit this. You must write something like:
424 |
425 | ```lua
426 | image = image:less(128):ifthenelse(128, image)
427 | ```
428 |
429 | to set all values less than 128 to 128.
430 |
431 | `__call` (ie. `()`) is overloaded to call the libvips `getpoint` operator.
432 | You can write:
433 |
434 | ```lua
435 | image = vips.Image.new_from_file("k2.jpg")
436 | r, g, b = image(10, 10)
437 | ```
438 |
439 | and `r`, `g`, `b` will be the RGB values for the pixel at coordinate (10, 10).
440 |
441 | `..` is overloaded to mean `bandjoin`.
442 |
443 | Use `im:bands()` to get the number of bands and `im:extract_band(N)` to extract a
444 | band (note bands number from zero). lua-vips does not overload `#` and `[]` for
445 | this, since mixing numbering from zero and one causes confusion.
446 |
447 | ## Convenience functions
448 |
449 | A set of convenience functions are also defined.
450 |
451 | ### `array = image:bandsplit()`
452 |
453 | This splits a many-band image into an array of one band images.
454 |
455 | ### `image:bandjoin()`
456 |
457 | The `bandjoin` operator takes an array of images as input. This can be awkward
458 | to call --- you must write:
459 |
460 |
461 | ```lua
462 | image = vips.Image.bandjoin{ image, image }
463 | ```
464 |
465 | to join an image to itself. Instead, `lua-vips` defines `bandjoin` as a member
466 | function, so you write:
467 |
468 | ```lua
469 | image = image:bandjoin(image)
470 | ```
471 |
472 | to join an image to itself, or perhaps:
473 |
474 | ```lua
475 | image = R:bandjoin{ G, B }
476 | ```
477 |
478 | to join three RGB bands. Constants work too, so you can write:
479 |
480 | ```lua
481 | image = image:bandjoin(255)
482 | image = R:bandjoin{ 128, 23 }
483 | ```
484 |
485 | The `bandrank` and `composite` operators works in the same way.
486 |
487 | ### `image = condition_image:ifthenelse(then_image, else_image [, options])`
488 |
489 | This uses the condition image to pick pixels between then and else. Unlike all
490 | other operators, if you use a constant for `then_image` or `else_image`, they
491 | first match to each other, and only match to the condition image if both then
492 | and else are constants.
493 |
494 | ### `image = image:sin()`
495 |
496 | Many vips arithmetic operators are implemented by larger operators which take
497 | an enum to set their action. For example, sine is implemented by the `math`
498 | operator, so you must write:
499 |
500 | ```lua
501 | image = image:math("sin")
502 | ```
503 |
504 | This is annoying, so a set of convenience functions are defined to enable you
505 | to write:
506 |
507 | ```lua
508 | image = image:sin()
509 | ```
510 |
511 | There are about 40 of these.
512 |
513 | ## Write
514 |
515 | You can write images to files, to ffi arrays, or to formatted strings.
516 |
517 | ### `image:write_to_file(filename [, options])`
518 |
519 | The filename suffix is used to pick the save operator. Just as with
520 | `new_from_file`, not all options will be correct for all file
521 | types. You can call savers directly if you wish, for example:
522 |
523 | ```lua
524 | image:jpegsave("x.jpg", { Q = 90 })
525 | ```
526 |
527 | ### `string = image:write_to_buffer(suffix [, options])`
528 |
529 | The suffix is used to pick the saver that is used to generate the result, so
530 | `".jpg"` will make a JPEG-formatted string. Again, you can call the savers
531 | directly if you wish, perhaps:
532 |
533 | ```lua
534 | local str = image:jpegsave_buffer{ Q = 90 }
535 | ```
536 |
537 | ### `memory = image:write_to_memory()`
538 |
539 | A large ffi char array is allocated and the image is rendered to it.
540 |
541 | ```lua
542 | local mem = image:write_to_memory()
543 | print("written ", ffi.sizeof(mem), "bytes to", mem)
544 | ```
545 |
546 | ### `ptr, size = image:write_to_memory_ptr()`
547 |
548 | An allocated char array pointer (GCd with a `ffi.gc` callback) and the length in bytes of the image data is directly returned from libvips (no intermediate FFI allocation).
549 |
550 | ## True Streaming
551 |
552 | When processing images an image library would usually read an image from a file into memory, decode and process it and finally write the encoded result into a file. The processing can only start when the image is fully read into memory and the writing can only start when the processing step is completed.
553 | Libvips can process images directly from a pipe and write directly to a pipe, without the need to read the whole image to memory before being able to start and without the need to finish processing before being able to start writing. This is achieved using a technique called true streaming. In this context there are sources and targets and the processing step happens from source to target. Sources can be created from files, memory or descriptors (like stdin) and targets can be created to files, memory or descriptors (like stdout). Here is an example:
554 |
555 | ```lua test.lua
556 | local vips = require "vips"
557 | local stdin, stdout = 0, 1
558 | local source = vips.Source.new_from_descriptor(stdin)
559 | local target = vips.Target.new_to_descriptor(stdout)
560 | local image = vips.Image.new_from_source(source, '', { access = 'sequential' })
561 | image = image:invert()
562 | image:write_to_target(target, '.jpg')
563 | ```
564 |
565 | Running this script in a Unix terminal via
566 | ```term
567 | curl https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/600px-Cat03.jpg | lua test.lua > out.jpg
568 | ```
569 |
570 | will feed a cat image from the internet into standard input, from which the Lua script reads and inverts it and writes it to standard output, where it is redirected to a file. This all happens simultaneously, so the processing and writing doesn't need to wait until the whole image is downloaded from the internet.
571 |
572 | ## Error handling
573 |
574 | Most `lua-vips` methods will call `error()` if they detect an error. Use
575 | `pcall()` to call a method and catch an error.
576 |
577 | Use `get_typeof` to test for a field of a certain name without throwing an
578 | error.
579 |
580 | ## The libvips operation cache
581 |
582 | libvips keeps a cache of recent operations, such as load, save, shrink, and
583 | so on. If you repeat an operation, you'll get the cached result back.
584 |
585 | It keeps track of the number of open files, allocated memory and cached
586 | operations, and will trim the cache if more than 100 files are open at once,
587 | more than 100mb of memory has been allocated, or more than 1,000 operations
588 | are being held.
589 |
590 | Normally this cache is useful and harmless, but for some applications you may
591 | want to change these values.
592 |
593 | ```lua
594 | -- set number of cached operations
595 | vips.cache_set_max(100)
596 | -- set maximum cache memory use
597 | vips.cache_set_max_mem(10 * 1024 * 1024)
598 | -- set maximum number of open files
599 | vips.cache_set_max_files(10)
600 | ```
601 |
602 | # Development
603 |
604 | ### Setup for Ubuntu
605 |
606 | Configure `luarocks` for a local tree
607 |
608 | ```shell
609 | luarocks help path
610 | ```
611 |
612 | append
613 |
614 | ```shell
615 | eval `luarocks path`
616 | export PATH="$HOME/.luarocks/bin:$PATH"
617 | ```
618 |
619 | to `~/.bashrc`.
620 |
621 | ### Install
622 |
623 | ```shell
624 | luarocks --local make
625 | ```
626 |
627 | ### Unit testing
628 |
629 | You need:
630 |
631 | ```shell
632 | luarocks --local install busted
633 | luarocks --local install luacov
634 | luarocks --local install say
635 | ```
636 |
637 | Then to run the test suite:
638 |
639 | ```shell
640 | busted .
641 | ```
642 |
643 | for verbose output:
644 |
645 | ```shell
646 | busted . -o gtest -v
647 | ```
648 |
649 | ### Linting and static analysis
650 |
651 | You need:
652 |
653 | ```shell
654 | luarocks --local install luacheck
655 | ```
656 |
657 | Then to run the linter:
658 |
659 | ```shell
660 | luacheck .
661 | ```
662 |
663 | ### Test
664 |
665 | Run the example script with:
666 |
667 | ```shell
668 | lua example/hello-world.lua
669 | ```
670 |
671 | ### Update rock
672 |
673 | ```shell
674 | rm *.src.rock
675 | luarocks upload lua-vips-1.1-12.rockspec --api-key=xxxxxxxxxxxxxx
676 | ```
677 |
678 | ### Links
679 |
680 | http://luajit.org/ext_ffi_api.html
681 |
682 | http://luajit.org/ext_ffi_semantics.html
683 |
684 | https://github.com/luarocks/luarocks/wiki/creating-a-rock
685 |
686 | https://olivinelabs.com/busted/
687 |
688 | ### Running on Windows using Mingw-w64
689 |
690 | Installing `lua-vips` on Windows is a bit harder than on Unix systems. We recommend using MinGW (Minimalist GNU for Windows) for the installation. Here are the steps:
691 |
692 | 1. Install [MSYS2](https://www.msys2.org/) to the default path.
693 | 2. Start Mingw-w64 64bit console from the start menu. Check that is says MINGW64. The following steps happen in that console.
694 | 3. Update MSYS2 using
695 | ```shell
696 | pacman -Syuu
697 | ```
698 | 4. Install the build tools (including Lua 5.4 and Luarocks) via
699 | ```shell
700 | pacman -S git make mingw-w64-x86_64-toolchain mingw-w64-x86_64-lua-luarocks
701 | ```
702 | 5. Install `libvips` with (optional) dependencies via
703 | ```shell
704 | pacman -S
705 | mingw-w64-x86_64-libvips
706 | mingw-w64-x86_64-openslide
707 | mingw-w64-x86_64-libheif
708 | mingw-w64-x86_64-libjxl
709 | mingw-w64-x86_64-imagemagick
710 | mingw-w64-x86_64-poppler
711 | ```
712 | 6. Optionally: If you want to use `lua-vips` with LuaJIT instead of Lua 5.4 install LuaJIT via
713 | ```shell
714 | pacman -S mingw-w64-x86_64-luajit
715 | luarocks config --scope system lua_version 5.1
716 | luarocks config --scope system lua_interpreter luajit.exe
717 | luarocks config --scope system lua_dir /mingw64
718 | luarocks config --scope system rocks_provided.luaffi-tkl 2.1-1
719 | ```
720 | 7. Install `lua-vips` via
721 | ```shell
722 | luarocks install lua-vips
723 | ```
724 | or clone the repository and run `luarocks make` in the `lua-vips` folder.
725 | 8. Add `C:\msys64\mingw64\bin` and `C:\msys64\usr\bin` to the top of your `PATH`
726 | environment variable in the Windows Advanced system settings and restart the console.
727 |
728 | 9. Run `lua` or `luajit` and try
729 | ```lua
730 | vips = require "vips"
731 | print(vips.Image.xyz(3,2))
732 | ```
733 |
734 | ### Running under Wine (Windows emulation on Linux)
735 |
736 | @jcupitt used the luapower all-in-one to get a 64-bit Windows LuaJIT build:
737 |
738 | https://luapower.com/
739 |
740 | LuaJIT on Windows searches `PATH` to find DLLs. You can't set this directly
741 | from Linux, you have to change the registry. See:
742 |
743 | https://www.winehq.org/docs/wineusr-guide/environment-variables
744 |
745 | Then add the `bin` area of the libvips Windows build to `PATH`.
746 |
747 | ```
748 | z:\home\john\GIT\build-win64\8.5\vips-dev-8.5\bin
749 | ```
750 |
751 | You must have no trailing backslash.
752 |
753 | Try LuaJIT:
754 |
755 | ```
756 | $ ~/packages/luajit/luapower-all-master/bin/mingw64/luajit.exe
757 | LuaJIT 2.1.0-beta2 -- Copyright (C) 2005-2016 Mike Pall.
758 | http://luajit.org/
759 | JIT: ON SSE2 SSE3 SSE4.1 fold cse dce fwd dse narrow loop abc sink fuse
760 | > print(os.getenv("PATH"))
761 | C:\windows\system32;C:\windows;C:\windows\system32\wbem;z:\home\john\GIT\build-win64\8.5\vips-dev-8.5\bin
762 | > ffi = require "ffi"
763 | > ffi.load("libvips-42.dll")
764 | > ^D
765 | ```
766 |
767 | The Windows luajit will pick up your `.luarocks/share/lua/5.1/vips.lua` install,
768 | so to test just install and run:
769 |
770 | ```
771 | $ ~/packages/luajit/luapower-all-master/bin/mingw64/luajit.exe
772 | LuaJIT 2.1.0-beta2 -- Copyright (C) 2005-2016 Mike Pall. http://luajit.org/
773 | JIT: ON SSE2 SSE3 SSE4.1 fold cse dce fwd dse narrow loop abc sink fuse
774 | > vips = require "vips"
775 | > x = vips.Image.new_from_file("z:\\data\\john\\pics\\k2.jpg")
776 | > print(x:width())
777 | 1450
778 | > x = vips.Image.text("hello", {dpi = 300})
779 | > x:write_to_file("x.png")
780 | >
781 | ```
782 |
783 |
--------------------------------------------------------------------------------