Skip to content

Commit a174534

Browse files
author
Connor Rigby
authored
Feature/property test (#5)
* Adds a single property test The property test is to check that a jpeg is equal to the same jpeg after it is turned into yuv and back to jpeg. Currently I only know how to get the hearders to be the same. The images look the same, but have slightly different binary. * Start working on a property test for solid colors * Testing out some ranges of generating numbers I was looking at the aggregates of the images properties and wanted to try to get some realistic numbers. This is a first pass. * Write test to check nif computation time. Set the functions to be dirty cpu bound so they don't block regular schedulers * Removes verbose in property tests Verbose creates a lot of output by default. Co-authored-by: Amos King <[email protected]> and Connor Rigby <[email protected]>
1 parent 6120801 commit a174534

File tree

5 files changed

+166
-19
lines changed

5 files changed

+166
-19
lines changed

c_src/turbojpeg_native/turbojpeg_native.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ int format_to_tjsamp(char* format) {
1111
return TJSAMP_422;
1212
} else if(strcmp(format, "I444") == 0) {
1313
return TJSAMP_444;
14+
} else if(strcmp(format, "GRAY") == 0) {
15+
return TJSAMP_GRAY;
1416
} else {
1517
return -1;
1618
}
@@ -28,7 +30,9 @@ const char* tjsamp_to_format(enum TJSAMP tjsamp) {
2830
return("I422");
2931
case(TJSAMP_444):
3032
return("I444");
31-
default:
33+
case(TJSAMP_GRAY):
34+
return("GRAY");
35+
default:
3236
return("unknown_format");
3337
}
3438
}

c_src/turbojpeg_native/turbojpeg_native.spec.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ module Turbojpeg.Native
22

33
spec yuv_to_jpeg(payload, width::int, height::int, quality::int, format::atom) :: {:ok :: label, payload} | {:error :: label, reason :: atom}
44
spec jpeg_to_yuv(payload) :: {:ok :: label, payload} | {:error :: label, reason :: atom}
5-
spec get_jpeg_header(payload) :: {:ok :: label, data::int} | {:error::label, reason::atom}
5+
spec get_jpeg_header(payload) :: {:ok :: label, data::int} | {:error::label, reason::atom}
6+
dirty :cpu, yuv_to_jpeg: 5, jpeg_to_yuv: 1, get_jpeg_header: 1

mix.exs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ defmodule Turbojpeg.MixProject do
3434
{:shmex, "~> 0.2.0"},
3535
{:bundlex, "~> 0.2.6"},
3636
{:membrane_core, "~> 0.5.0"},
37-
{:ex_doc, "~> 0.21.3", only: [:dev], runtime: false}
37+
{:ex_doc, "~> 0.21.3", only: [:dev], runtime: false},
38+
{:propcheck, "~> 1.2.0", only: [:test]},
39+
{:mogrify, github: "ConnorRigby/mogrify", branch: "master", only: [:test, :dev]}
3840
]
3941
end
4042

mix.lock

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
%{
2-
"bunch": {:hex, :bunch, "1.2.0", "f26c6c84e2e5e245620c73a0a4560ca91a75b25744012537e055426568916ab7", [:mix], [], "hexpm"},
3-
"bunch_native": {:hex, :bunch_native, "0.2.1", "0227d2a751a32f8c0b77dfec57c8dc7216351720c9c755c467e6d9387467fd1f", [:mix], [{:bundlex, "~> 0.2.7", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm"},
4-
"bundlex": {:hex, :bundlex, "0.2.7", "8f46199bf4cf84a60cdfc142edeafbab37040167acc35dda8aa70433f2ff8162", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:secure_random, "~> 0.5", [hex: :secure_random, repo: "hexpm", optional: false]}], "hexpm"},
5-
"coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm"},
6-
"earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm"},
7-
"ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
8-
"makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
9-
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
10-
"membrane_core": {:hex, :membrane_core, "0.5.0", "6fd6de1158ff1e8d58ec381d25b71b23b349718a785d6de67ffe6866e6a05b0a", [:mix], [{:bunch, "~> 1.2", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 2.0", [hex: :ratio, repo: "hexpm", optional: false]}], "hexpm"},
11-
"nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm"},
12-
"numbers": {:hex, :numbers, "5.2.0", "34515afc34b005b347128ea1bf5a5807f69b07836d9f09611471282e04adabf3", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
13-
"qex": {:hex, :qex, "0.5.0", "5a3a9becf67d4006377c4c247ffdaaa8ae5b3634a0caadb788dc24d6125068f4", [:mix], [], "hexpm"},
14-
"ratio": {:hex, :ratio, "2.4.0", "bd073c82871ea9d900243bc0b351e5274484ec469f7e91eaa83de6468ae9cdcf", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:numbers, "~> 5.2.0", [hex: :numbers, repo: "hexpm", optional: false]}], "hexpm"},
15-
"secure_random": {:hex, :secure_random, "0.5.1", "c5532b37c89d175c328f5196a0c2a5680b15ebce3e654da37129a9fe40ebf51b", [:mix], [], "hexpm"},
16-
"shmex": {:hex, :shmex, "0.2.0", "2dc5e3919171e69993729fab2eee5bde6b679a4c7da91fd88e41c4254cc65fef", [:mix], [{:bunch_native, "~> 0.2.0", [hex: :bunch_native, repo: "hexpm", optional: false]}, {:bundlex, "~> 0.2.4", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm"},
17-
"unifex": {:hex, :unifex, "0.2.5", "a279c09e0bc2a9a50b5fa0552c36783964601f803bb27813122bb17a03be4ebf", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 0.2.0", [hex: :bundlex, repo: "hexpm", optional: false]}, {:shmex, "~> 0.2.0", [hex: :shmex, repo: "hexpm", optional: false]}], "hexpm"},
2+
"bunch": {:hex, :bunch, "1.2.0", "f26c6c84e2e5e245620c73a0a4560ca91a75b25744012537e055426568916ab7", [:mix], [], "hexpm", "66c1ea8716a7c03124f3bf62c0b167f7294c4948d1688bd2027dadaa4ec032de"},
3+
"bunch_native": {:hex, :bunch_native, "0.2.1", "0227d2a751a32f8c0b77dfec57c8dc7216351720c9c755c467e6d9387467fd1f", [:mix], [{:bundlex, "~> 0.2.7", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "f0819b2f9f78086447ac7459a8e7b6e25ad535b9d3a4f9469253c552137de6b4"},
4+
"bundlex": {:hex, :bundlex, "0.2.7", "8f46199bf4cf84a60cdfc142edeafbab37040167acc35dda8aa70433f2ff8162", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:secure_random, "~> 0.5", [hex: :secure_random, repo: "hexpm", optional: false]}], "hexpm", "5751b4bb5dd0576ca70519239e2ee7b495b17e4bbfbe3ef1f60ae0c12c0e4fe9"},
5+
"coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"},
6+
"earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
7+
"ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"},
8+
"makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"},
9+
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"},
10+
"membrane_core": {:hex, :membrane_core, "0.5.0", "6fd6de1158ff1e8d58ec381d25b71b23b349718a785d6de67ffe6866e6a05b0a", [:mix], [{:bunch, "~> 1.2", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 2.0", [hex: :ratio, repo: "hexpm", optional: false]}], "hexpm", "4e9b3b5e8dd0a7836ed9e3687a48607881d47d6c0b5ed02dd3d32e5b4ad4d312"},
11+
"mogrify": {:git, "https://github.com/ConnorRigby/mogrify.git", "2cea3c76873d75893dac99acfe280e242672ddfc", [branch: "master"]},
12+
"nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
13+
"numbers": {:hex, :numbers, "5.2.0", "34515afc34b005b347128ea1bf5a5807f69b07836d9f09611471282e04adabf3", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "a0565589e41a9a76834f2666abfac022601cf391218862725dae4e794216712e"},
14+
"propcheck": {:hex, :propcheck, "1.2.0", "e2b84f2f1a4c46b6b2aa22a0f6ddf97696f99d4a5c8f71d45f6519741e727eca", [:mix], [{:proper, "~> 1.3", [hex: :proper, repo: "hexpm", optional: false]}], "hexpm", "0f4fb2393fa5321ba7f23a8feabc861ac825e62d9e1db4aa35e39cace4c5c08d"},
15+
"proper": {:hex, :proper, "1.3.0", "c1acd51c51da17a2fe91d7a6fc6a0c25a6a9849d8dc77093533109d1218d8457", [:make, :mix, :rebar3], [], "hexpm", "4aa192fccddd03fdbe50fef620be9d4d2f92635b54f55fb83aec185994403cbc"},
16+
"qex": {:hex, :qex, "0.5.0", "5a3a9becf67d4006377c4c247ffdaaa8ae5b3634a0caadb788dc24d6125068f4", [:mix], [], "hexpm", "4ad6f6421163cd8204509a119a5c9813cbb969cfb8d802a9dc49b968bffbac2a"},
17+
"ratio": {:hex, :ratio, "2.4.0", "bd073c82871ea9d900243bc0b351e5274484ec469f7e91eaa83de6468ae9cdcf", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:numbers, "~> 5.2.0", [hex: :numbers, repo: "hexpm", optional: false]}], "hexpm", "3623a323a29b7a2b51914efe3a612cca812bd354f1dd2ffc4ac62058b72447e8"},
18+
"secure_random": {:hex, :secure_random, "0.5.1", "c5532b37c89d175c328f5196a0c2a5680b15ebce3e654da37129a9fe40ebf51b", [:mix], [], "hexpm", "1b9754f15e3940a143baafd19da12293f100044df69ea12db5d72878312ae6ab"},
19+
"shmex": {:hex, :shmex, "0.2.0", "2dc5e3919171e69993729fab2eee5bde6b679a4c7da91fd88e41c4254cc65fef", [:mix], [{:bunch_native, "~> 0.2.0", [hex: :bunch_native, repo: "hexpm", optional: false]}, {:bundlex, "~> 0.2.4", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "ac0af802c8934a84a2e7255490568a254b16188acb6358ee24d7d53f76852fc0"},
20+
"unifex": {:hex, :unifex, "0.2.5", "a279c09e0bc2a9a50b5fa0552c36783964601f803bb27813122bb17a03be4ebf", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 0.2.0", [hex: :bundlex, repo: "hexpm", optional: false]}, {:shmex, "~> 0.2.0", [hex: :shmex, repo: "hexpm", optional: false]}], "hexpm", "450df31fc1a5e66896bc39e33823db309c124091d37130e07828560ee041fe31"},
1821
}

test/turbojpeg_test.exs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
defmodule TurbojpegTest do
22
use ExUnit.Case
3+
use PropCheck, default_opts: [numtests: 10]
4+
use Mogrify.Options
5+
36
@jpeg_header <<255, 216, 255>>
47
@i420_fixture "fixture/i420.yuv"
58
@ff0000_fixture "fixture/ff0000_i444.jpg"
@@ -25,4 +28,138 @@ defmodule TurbojpegTest do
2528
assert result.height == 64
2629
assert result.format == :I444
2730
end
31+
32+
@tag [timeout: :infinity]
33+
property "encoding an image is fast", numtests: 8 do
34+
forall [width, height] <- [
35+
width(100),
36+
height(100)
37+
] do
38+
{jpeg_creation_micros, jpeg} =
39+
:timer.tc(fn ->
40+
jpeg =
41+
%Mogrify.Image{}
42+
|> Mogrify.custom("size", "#{width}x#{height}")
43+
|> Mogrify.custom("seed", 43)
44+
|> Mogrify.custom("plasma", "fractal")
45+
|> Mogrify.custom("sampling-factor", "4:4:4")
46+
|> Mogrify.custom("stdout", "jpg:-")
47+
|> Mogrify.create(buffer: true)
48+
49+
Shmex.new(jpeg.buffer)
50+
end)
51+
52+
{native_micros, ret} =
53+
:timer.tc(fn ->
54+
Turbojpeg.Native.jpeg_to_yuv(jpeg)
55+
end)
56+
57+
assert match?({:ok, _}, ret)
58+
aggregate(true, result: {to_range(10, jpeg_creation_micros), to_range(1024, jpeg.size)})
59+
end
60+
end
61+
62+
property "solid color jpeg complementary" do
63+
forall [width, height, seed, {r, g, b}, {sampling_factor, _format}] <- [
64+
width(),
65+
height(),
66+
seed(),
67+
rgb(),
68+
format()
69+
] do
70+
color = :io_lib.format('#~2.16.0B~2.16.0B~2.16.0B', [r, g, b])
71+
72+
jpeg =
73+
%Mogrify.Image{}
74+
|> Mogrify.custom("size", "#{width}x#{height}")
75+
|> Mogrify.custom("seed", seed)
76+
|> Mogrify.custom("canvas", to_string(color))
77+
|> Mogrify.custom("sampling-factor", sampling_factor)
78+
|> Mogrify.custom("stdout", "jpg:-")
79+
|> Mogrify.create(buffer: true)
80+
81+
jpeg = Shmex.new(jpeg.buffer)
82+
{:ok, yuv} = Turbojpeg.Native.jpeg_to_yuv(jpeg)
83+
84+
{:ok, original_header} = Turbojpeg.Native.get_jpeg_header(jpeg)
85+
86+
{:ok, new_jpeg} =
87+
Turbojpeg.Native.yuv_to_jpeg(yuv, width, height, 100, original_header.format)
88+
89+
{:ok, new_header} = Turbojpeg.Native.get_jpeg_header(new_jpeg)
90+
91+
assert original_header == new_header
92+
93+
aggregate(true,
94+
r: to_range(51, r),
95+
g: to_range(51, g),
96+
b: to_range(51, b)
97+
)
98+
end
99+
end
100+
101+
property "jpeg and yuv conversion are complementary after running through the tool once" do
102+
forall [width, height, seed, {sampling_factor, format}] <- [
103+
width(),
104+
height(),
105+
seed(),
106+
format()
107+
] do
108+
jpeg =
109+
%Mogrify.Image{}
110+
|> Mogrify.custom("size", "#{width}x#{height}")
111+
|> Mogrify.custom("seed", seed)
112+
|> Mogrify.custom("plasma", "fractal")
113+
|> Mogrify.custom("sampling-factor", sampling_factor)
114+
|> Mogrify.custom("stdout", "jpg:-")
115+
|> Mogrify.create(buffer: true)
116+
117+
jpeg = Shmex.new(jpeg.buffer)
118+
{:ok, yuv} = Turbojpeg.Native.jpeg_to_yuv(jpeg)
119+
{:ok, new_jpeg} = Turbojpeg.Native.yuv_to_jpeg(yuv, width, height, 100, format)
120+
{:ok, original_header} = Turbojpeg.Native.get_jpeg_header(jpeg)
121+
{:ok, new_header} = Turbojpeg.Native.get_jpeg_header(new_jpeg)
122+
assert original_header == new_header
123+
124+
aggregate(true,
125+
size: to_range(10_000, width * height) |> as_bytes(1024),
126+
width: to_range(10, width)
127+
)
128+
end
129+
end
130+
131+
def to_range(size, n) do
132+
base = div(n, size)
133+
{base * size, (base + 1) * size}
134+
end
135+
136+
def as_bytes({min, max}, size) do
137+
{div(min, size), div(max, size)}
138+
end
139+
140+
def width(multiplier \\ 50) do
141+
dimension(multiplier)
142+
end
143+
144+
def height(multiplier \\ 50) do
145+
dimension(multiplier)
146+
end
147+
148+
def dimension(multiplier) do
149+
sized(s, resize(s * multiplier, pos_integer()))
150+
end
151+
152+
def seed do
153+
pos_integer()
154+
end
155+
156+
def format do
157+
oneof([{"4:2:0", :I420}, {"4:4:4", :I444}, {"4:2:2", :I422}])
158+
end
159+
160+
def rgb do
161+
{color(), color(), color()}
162+
end
163+
164+
def color(), do: integer(0, 255)
28165
end

0 commit comments

Comments
 (0)