Skip to content

Commit d7df79c

Browse files
committed
Make new_from_enum and write_to_stream accept keyword opts
1 parent cc6a11c commit d7df79c

File tree

10 files changed

+221
-108
lines changed

10 files changed

+221
-108
lines changed

c_src/vips_foreign.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,6 @@ ERL_NIF_TERM nif_foreign_find_save(ErlNifEnv *env, int argc,
135135
return ret;
136136
}
137137

138-
139138
ERL_NIF_TERM nif_foreign_find_load_source(ErlNifEnv *env, int argc,
140139
const ERL_NIF_TERM argv[]) {
141140
ASSERT_ARGC(argc, 1);
@@ -155,7 +154,8 @@ ERL_NIF_TERM nif_foreign_find_load_source(ErlNifEnv *env, int argc,
155154
name = vips_foreign_find_load_source(source);
156155

157156
if (!name) {
158-
error("Failed to find the loader for the source. error: %s", vips_error_buffer());
157+
error("Failed to find the loader for the source. error: %s",
158+
vips_error_buffer());
159159
vips_error_clear();
160160
ret = make_error(env, "Failed to find loader for the source");
161161
goto exit;
@@ -187,7 +187,8 @@ ERL_NIF_TERM nif_foreign_find_save_target(ErlNifEnv *env, int argc,
187187
name = vips_foreign_find_save_target(suffix);
188188

189189
if (!name) {
190-
error("Failed to find saver for the target. error: %s", vips_error_buffer());
190+
error("Failed to find saver for the target. error: %s",
191+
vips_error_buffer());
191192
vips_error_clear();
192193
ret = make_error(env, "Failed to find saver for the target");
193194
goto exit;

c_src/vips_foreign.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ ERL_NIF_TERM nif_foreign_find_save(ErlNifEnv *env, int argc,
1616
const ERL_NIF_TERM argv[]);
1717

1818
ERL_NIF_TERM nif_foreign_find_load_source(ErlNifEnv *env, int argc,
19-
const ERL_NIF_TERM argv[]);
19+
const ERL_NIF_TERM argv[]);
2020

2121
ERL_NIF_TERM nif_foreign_find_save_target(ErlNifEnv *env, int argc,
22-
const ERL_NIF_TERM argv[]);
22+
const ERL_NIF_TERM argv[]);
2323

2424
ERL_NIF_TERM nif_foreign_get_suffixes(ErlNifEnv *env, int argc,
2525
const ERL_NIF_TERM argv[]);

c_src/vix.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,13 @@ static ErlNifFunc nif_funcs[] = {
153153
0},
154154

155155
/* VipsForeign */
156-
{"nif_foreign_find_load", 1, nif_foreign_find_load, ERL_NIF_DIRTY_JOB_IO_BOUND}, // it might read bytes form the file
156+
{"nif_foreign_find_load", 1, nif_foreign_find_load,
157+
ERL_NIF_DIRTY_JOB_IO_BOUND}, // it might read bytes form the file
157158
{"nif_foreign_find_save", 1, nif_foreign_find_save, 0},
158159
{"nif_foreign_find_load_buffer", 1, nif_foreign_find_load_buffer, 0},
159160
{"nif_foreign_find_save_buffer", 1, nif_foreign_find_save_buffer, 0},
160-
{"nif_foreign_find_load_source", 1, nif_foreign_find_load_source, ERL_NIF_DIRTY_JOB_IO_BOUND}, // it might read bytes from source
161+
{"nif_foreign_find_load_source", 1, nif_foreign_find_load_source,
162+
ERL_NIF_DIRTY_JOB_IO_BOUND}, // it might read bytes from source
161163
{"nif_foreign_find_save_target", 1, nif_foreign_find_save_target, 0},
162164
{"nif_foreign_get_suffixes", 0, nif_foreign_get_suffixes, 0},
163165
{"nif_foreign_get_loader_suffixes", 0, nif_foreign_get_loader_suffixes, 0},

lib/vix/source_pipe.ex

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ defmodule Vix.SourcePipe do
1414
defstruct bin: [], client_pid: nil
1515
end
1616

17+
@spec new() :: {pid, Vix.Vips.Source.t()}
1718
def new do
1819
{:ok, pipe} = GenServer.start_link(__MODULE__, nil)
1920
source = GenServer.call(pipe, :source, :infinity)
@@ -37,7 +38,13 @@ defmodule Vix.SourcePipe do
3738
def handle_continue(nil, _) do
3839
case Nif.nif_source_new() do
3940
{:ok, {fd, source}} ->
40-
{:noreply, %SourcePipe{fd: fd, pending: %Pending{}, source: source}}
41+
source_pipe = %SourcePipe{
42+
fd: fd,
43+
pending: %Pending{},
44+
source: %Vix.Vips.Source{ref: source}
45+
}
46+
47+
{:noreply, source_pipe}
4148

4249
{:error, reason} ->
4350
{:stop, reason, nil}

lib/vix/target_pipe.ex

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,20 @@ defmodule Vix.TargetPipe do
77

88
@moduledoc false
99

10+
@type t() :: struct
11+
1012
defstruct [:fd, :pending, :task_result, :task_pid]
1113

1214
defmodule Pending do
1315
@moduledoc false
14-
defstruct size: nil, client_pid: nil
16+
defstruct size: nil, client_pid: nil, opts: []
1517
end
1618

1719
@default_buffer_size 65_535
1820

19-
def new(vips_image, suffix) do
20-
GenServer.start_link(__MODULE__, %{image: vips_image, suffix: suffix})
21+
@spec new(Vix.Vips.Image.t(), String.t(), keyword) :: GenServer.on_start()
22+
def new(image, suffix, opts) do
23+
GenServer.start_link(__MODULE__, %{image: image, suffix: suffix, opts: opts})
2124
end
2225

2326
def read(process, max_size \\ @default_buffer_size)
@@ -31,15 +34,15 @@ defmodule Vix.TargetPipe do
3134

3235
# Server
3336

34-
def init(%{image: image, suffix: suffix}) do
37+
def init(%{image: image, suffix: suffix, opts: opts}) do
3538
Process.flag(:trap_exit, true)
36-
{:ok, nil, {:continue, %{image: image, suffix: suffix}}}
39+
{:ok, nil, {:continue, %{image: image, suffix: suffix, opts: opts}}}
3740
end
3841

39-
def handle_continue(%{image: image, suffix: suffix}, _) do
42+
def handle_continue(%{image: image, suffix: suffix, opts: opts}, _) do
4043
case Nif.nif_target_new() do
4144
{:ok, {fd, target}} ->
42-
pid = start_task(image, target, suffix)
45+
pid = start_task(image, %Vix.Vips.Target{ref: target}, suffix, opts)
4346
{:noreply, %TargetPipe{fd: fd, task_pid: pid, pending: %Pending{}}}
4447

4548
{:error, reason} ->
@@ -98,9 +101,21 @@ defmodule Vix.TargetPipe do
98101
{:noreply, state}
99102
end
100103

101-
defp start_task(image, target, suffix) do
104+
@spec start_task(Vix.Vips.Image.t(), Vix.Vips.Target.t(), String.t(), keyword) :: pid
105+
defp start_task(%Vix.Vips.Image{} = image, target, suffix, []) do
102106
spawn_link(fn ->
103-
result = Nif.nif_image_to_target(image, target, suffix)
107+
result = Nif.nif_image_to_target(image.ref, target.ref, suffix)
108+
Process.exit(self(), result)
109+
end)
110+
end
111+
112+
defp start_task(image, target, suffix, opts) do
113+
spawn_link(fn ->
114+
result =
115+
with {:ok, saver} <- Vix.Vips.Foreign.find_save_target(suffix) do
116+
Vix.Vips.Operation.Helper.operation_call(saver, [image, target], opts)
117+
end
118+
104119
Process.exit(self(), result)
105120
end)
106121
end

lib/vix/vips/foreign.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ defmodule Vix.Vips.Foreign do
3232
end
3333

3434
@spec find_load_source(Vix.Vips.Source.t()) :: {:ok, operation_name} | {:error, String.t()}
35-
def find_load_source(source) do
36-
Nif.nif_foreign_find_load_source(source)
35+
def find_load_source(%Vix.Vips.Source{ref: vips_source}) do
36+
Nif.nif_foreign_find_load_source(vips_source)
3737
end
3838

3939
@spec find_save_target(String.t()) :: {:ok, operation_name} | {:error, String.t()}

lib/vix/vips/image.ex

Lines changed: 64 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,7 @@ defmodule Vix.Vips.Image do
447447
@doc since: "0.31.0"
448448
def new_from_file(path, opts) do
449449
with {:ok, path} <- normalize_path(path),
450+
:ok <- validate_options(opts),
450451
{:ok, loader} <- Vix.Vips.Foreign.find_load(path),
451452
{:ok, {ref, _optional}} <- Operation.Helper.operation_call(loader, [path], opts) do
452453
{:ok, wrap_type(ref)}
@@ -504,7 +505,8 @@ defmodule Vix.Vips.Image do
504505
"""
505506
@spec new_from_buffer(binary(), keyword()) :: {:ok, t()} | {:error, term()}
506507
def new_from_buffer(bin, opts \\ []) do
507-
with {:ok, loader} <- Vix.Vips.Foreign.find_load_buffer(bin),
508+
with :ok <- validate_options(opts),
509+
{:ok, loader} <- Vix.Vips.Foreign.find_load_buffer(bin),
508510
{:ok, {ref, _optional}} <- Operation.Helper.operation_call(loader, [bin], opts) do
509511
{:ok, wrap_type(ref)}
510512
end
@@ -580,26 +582,18 @@ defmodule Vix.Vips.Image do
580582
:ok = Image.write_to_file(image, "puppies.png")
581583
```
582584
583-
Optional param `opts` string is passed to the image loader. It is a string
584-
of the format "[name=value,...]".
585+
Optional param `opts` is passed to the image loader. Available
586+
options depends on the format.
585587
586588
```elixir
587-
Image.new_from_enum(stream, "[shrink=2]")
588-
```
589-
590-
Will read the stream with downsampling by a factor of two.
591-
592-
The full set of options available depend upon the image format. You
593-
can find all options available at the command-line. To see a summary
594-
of the available options for the JPEG loader:
595-
596-
```shell
597-
$ vips jpegload_source
589+
Image.new_from_enum(stream, [shrink: 2])
598590
```
599591
592+
You can find all options available for a format under operation function in
593+
[Operation](./search.html?q=load+-buffer+-filename+-profile) module.
600594
"""
601-
@spec new_from_enum(Enumerable.t(), String.t()) :: {:ok, t()} | {:error, term()}
602-
def new_from_enum(enum, opts \\ "") do
595+
@spec new_from_enum(Enumerable.t(), String.t() | keyword) :: {:ok, t()} | {:error, term()}
596+
def new_from_enum(enum, opts \\ []) do
603597
parent = self()
604598

605599
pid =
@@ -625,9 +619,17 @@ defmodule Vix.Vips.Image do
625619
end)
626620

627621
receive do
628-
{^pid, source} ->
629-
Nif.nif_image_new_from_source(source, opts)
622+
# for backward compatibility
623+
{^pid, source} when is_binary(opts) ->
624+
Nif.nif_image_new_from_source(source.ref, opts)
630625
|> wrap_type()
626+
627+
{^pid, source} ->
628+
with :ok <- validate_options(opts),
629+
{:ok, loader} <- Vix.Vips.Foreign.find_load_source(source),
630+
{:ok, {ref, _optional}} <- Operation.Helper.operation_call(loader, [source], opts) do
631+
{:ok, wrap_type(ref)}
632+
end
631633
end
632634
end
633635

@@ -649,29 +651,22 @@ defmodule Vix.Vips.Image do
649651
|> Stream.run()
650652
```
651653
652-
Second param `suffix` determines the format of the output
653-
stream. Save options may be appended to the suffix as
654-
"[name=value,...]".
654+
Optional param `opts` is passed to the image saver. Available
655+
options depends on the file format. You can find all options
656+
available for a format under operation function in
657+
[Operation](./search.html?q=save+buffer+-filename+-profile) module.
655658
656659
```elixir
657-
Image.write_to_stream(vips_image, ".jpg[Q=90]")
660+
Image.write_to_stream(vips_image, ".jpg", Q: 90)
658661
```
659-
660-
Options are specific to save operation. You can find out all
661-
available options for the save operation at command line. For
662-
example:
663-
664-
```shell
665-
$ vips jpegsave_target
666-
```
667-
668662
"""
669-
@spec write_to_stream(t(), String.t()) :: Enumerable.t()
670-
def write_to_stream(%Image{ref: vips_image}, suffix) do
663+
@spec write_to_stream(t(), String.t(), keyword) :: Enumerable.t()
664+
665+
@doc since: "0.32.0"
666+
def write_to_stream(%Image{ref: _} = image, suffix, opts) do
671667
Stream.resource(
672668
fn ->
673-
{:ok, pipe} = Vix.TargetPipe.new(vips_image, suffix)
674-
pipe
669+
init_write_stream(image, suffix, opts)
675670
end,
676671
fn pipe ->
677672
ret = Vix.TargetPipe.read(pipe)
@@ -693,6 +688,10 @@ defmodule Vix.Vips.Image do
693688
)
694689
end
695690

691+
def write_to_stream(%Image{ref: _} = image, suffix) do
692+
write_to_stream(%Image{ref: _} = image, suffix, [])
693+
end
694+
696695
@doc """
697696
Converts an Image to a nested list.
698697
@@ -804,12 +803,9 @@ defmodule Vix.Vips.Image do
804803
def write_to_file(%Image{ref: _} = image, path, opts) do
805804
path = normalize_string(Path.expand(path))
806805

807-
case Vix.Vips.Foreign.find_save(path) do
808-
{:ok, saver} ->
809-
Operation.Helper.operation_call(saver, [image, path], opts)
810-
811-
error ->
812-
error
806+
with :ok <- validate_options(opts),
807+
{:ok, saver} <- Vix.Vips.Foreign.find_save(path) do
808+
Operation.Helper.operation_call(saver, [image, path], opts)
813809
end
814810
end
815811

@@ -834,7 +830,7 @@ defmodule Vix.Vips.Image do
834830
835831
```elixir
836832
# returns image in JPEG format as binary with Q factor set 90
837-
Image.write_to_buffer(img, ".jpg", [Q: 90])
833+
Image.write_to_buffer(img, ".jpg", Q: 90)
838834
```
839835
840836
If you want more control over the saver, Use specific format saver
@@ -845,12 +841,9 @@ defmodule Vix.Vips.Image do
845841

846842
@doc since: "0.32.0"
847843
def write_to_buffer(%Image{ref: _} = image, suffix, opts) do
848-
case Vix.Vips.Foreign.find_save_buffer(normalize_string(suffix)) do
849-
{:ok, saver} ->
850-
Operation.Helper.operation_call(saver, [image], opts)
851-
852-
error ->
853-
error
844+
with :ok <- validate_options(opts),
845+
{:ok, saver} <- Vix.Vips.Foreign.find_save_buffer(normalize_string(suffix)) do
846+
Operation.Helper.operation_call(saver, [image], opts)
854847
end
855848
end
856849

@@ -1650,4 +1643,27 @@ defmodule Vix.Vips.Image do
16501643
true -> raise ArgumentError, "integer size must be <= 32bit"
16511644
end
16521645
end
1646+
1647+
@spec validate_options(keyword) :: :ok | {:error, String.t()}
1648+
defp validate_options(opts) do
1649+
if Keyword.keyword?(opts) do
1650+
:ok
1651+
else
1652+
{:error, "Opts must be a keyword list"}
1653+
end
1654+
end
1655+
1656+
@spec init_write_stream(Image.t(), String.t(), keyword) :: term | no_return
1657+
defp init_write_stream(image, suffix, opts) do
1658+
with :ok <- validate_options(opts),
1659+
{:ok, pipe} <- Vix.TargetPipe.new(image, suffix, opts) do
1660+
pipe
1661+
else
1662+
{:error, reason} when is_binary(reason) ->
1663+
raise Error, reason
1664+
1665+
{:error, reason} ->
1666+
raise Error, inspect(reason)
1667+
end
1668+
end
16531669
end

lib/vix/vips/source.ex

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ defmodule Vix.Vips.Source do
22
@moduledoc false
33

44
alias Vix.Type
5+
alias __MODULE__
56

67
@behaviour Type
7-
@opaque t() :: reference()
8+
9+
@type t() :: %Source{ref: reference}
10+
11+
defstruct [:ref]
812

913
@impl Type
1014
def typespec do
@@ -17,8 +21,16 @@ defmodule Vix.Vips.Source do
1721
def default(nil), do: :unsupported
1822

1923
@impl Type
20-
def to_nif_term(_value, _data), do: raise("VipsSource is not implemented yet")
24+
def to_nif_term(source, _data) do
25+
case source do
26+
%Source{ref: ref} ->
27+
ref
28+
29+
value ->
30+
raise ArgumentError, message: "expected Vix.Vips.Source given: #{inspect(value)}"
31+
end
32+
end
2133

2234
@impl Type
23-
def to_erl_term(_value), do: raise("VipsSource is not implemented yet")
35+
def to_erl_term(ref), do: %Source{ref: ref}
2436
end

0 commit comments

Comments
 (0)