Skip to content

Commit faaabb9

Browse files
authored
feat(decode): add keyword preferred_size (#18)
1 parent fa63352 commit faaabb9

File tree

6 files changed

+101
-4
lines changed

6 files changed

+101
-4
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ filename = testimage("earth", download_only=true)
8383
@btime jpeg_decode(Gray, filename; scale_ratio=0.25); # 63.119 ms (6 allocations: 1.08 MiB)
8484
```
8585

86+
An exclusive alternative to `scale_ratio` is `preferred_size`:
87+
88+
```julia
89+
# minimal `scale_ratio` that output size is greater than or equal to (512, 512)
90+
jpeg_decode(filename; preferred_size=(512, 512)) # size: (751, 750)
91+
# minimal `scale_ratio` that output size is less than or equal to (512, 512)
92+
jpeg_decode(filename; preferred_size=(<=, (512, 512))) # size: (376, 375)
93+
```
94+
8695
## Acknowledgements
8796

8897
The purpose of this project is to replace [ImageMagick.jl] with [ImageIO]. Steven G. Johnson first

docs/src/index.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,12 @@ filename = testimage("earth", download_only=true)
8080
# process only a few pixels for luminance component
8181
@btime jpeg_decode(Gray, filename; scale_ratio=0.25); # 63.119 ms (6 allocations: 1.08 MiB)
8282
```
83+
84+
An exclusive alternative to `scale_ratio` is `preferred_size`:
85+
86+
```julia
87+
# minimal `scale_ratio` that output size is greater than or equal to (512, 512)
88+
jpeg_decode(filename; preferred_size=(512, 512)) # size: (751, 750)
89+
# minimal `scale_ratio` that output size is less than or equal to (512, 512)
90+
jpeg_decode(filename; preferred_size=(<=, (512, 512))) # size: (376, 375)
91+
```

src/decode.jl

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,14 @@ Decode the JPEG image as colorant matrix. The source data can be either a filena
1212
before encoding. The default value is `false`.
1313
- `scale_ratio::Real`: scale the image by ratio `scale_ratio` in `M/8` with `M ∈ 1:16`. The
1414
default value is `1`. For values are not in the range, they will be mapped to the nearest
15-
value, e.g., `0.3 => 2/8` and `0.35 => 3/8`.
15+
value, e.g., `0.3 => 2/8` and `0.35 => 3/8`. `scale_ratio` and `preferred_size` may not be
16+
used together.
17+
- `preferred_size::Tuple`: infer the minimal `scale_ratio` that `all(size(out) .>=
18+
preferred_size))` holds. It can optionally be `(op, preferred_size)` format, with compare
19+
operation `op` be one of `>`, `>=`, `<` or `<=`. If `op in (>=, >)` then it gets the
20+
minimal `scale_ratio`, otherwise it gets the maximum `scale_ratio` for `op in (<=, <)`.
21+
`scale_ratio` and `preferred_size` may not be used together. The `preferred_size`
22+
dimensions are not affected by keyword `transpose`.
1623
1724
# Examples
1825
@@ -52,7 +59,8 @@ function jpeg_decode(
5259
::Type{CT},
5360
data::Vector{UInt8};
5461
transpose=false,
55-
scale_ratio=1) where CT<:Colorant
62+
scale_ratio::Union{Nothing,Real}=nothing,
63+
preferred_size::Union{Nothing,Tuple}=nothing) where CT<:Colorant
5664
_jpeg_check_bytes(data)
5765
out_CT, jpeg_cls = _jpeg_out_color_space(CT)
5866

@@ -66,7 +74,14 @@ function jpeg_decode(
6674
LibJpeg.jpeg_read_header(cinfo_ref, true)
6775

6876
# set decompression parameters, if given
69-
r = _cal_scale_ratio(scale_ratio)
77+
if !isnothing(preferred_size) && !transpose
78+
if preferred_size[1] isa Function
79+
preferred_size = (preferred_size[1], reverse(preferred_size[2]))
80+
else
81+
preferred_size = reverse(preferred_size)
82+
end
83+
end
84+
r = _cal_scale_ratio(scale_ratio, preferred_size, cinfo)
7085
cinfo.scale_num, cinfo.scale_denom = r.num, r.den
7186
cinfo.out_color_space = jpeg_cls
7287

@@ -127,7 +142,37 @@ _jpeg_decode!(out::Matrix{<:Colorant}, cinfo::LibJpeg.jpeg_decompress_struct) =
127142

128143
# libjpeg-turbo only supports ratio M/8 with M from 1 to 16
129144
const _allowed_scale_ratios = ntuple(i->i//8, 16)
130-
_cal_scale_ratio(r::Real) = _allowed_scale_ratios[findmin(x->abs(x-r), _allowed_scale_ratios)[2]]
145+
function _cal_scale_ratio(::Real, ::Tuple, cinfo)
146+
throw(ArgumentError("Keywords `aspect_ratio` and `preferred_size` are exclusive to each other: you should only specify one of them."))
147+
end
148+
_cal_scale_ratio(::Nothing, ::Nothing, cinfo) = 1//1
149+
_cal_scale_ratio(r::Real, ::Nothing, cinfo) = _allowed_scale_ratios[findmin(x->abs(x-r), _allowed_scale_ratios)[2]]
150+
function _cal_scale_ratio(::Nothing, preferred_size::Tuple, cinfo)
151+
cinfo.scale_num, cinfo.scale_denom = 1, 1
152+
LibJpeg.jpeg_calc_output_dimensions(Ref(cinfo))
153+
out_size = (Int(cinfo.output_width), Int(cinfo.output_height))
154+
op, preferred_size = if preferred_size[1] isa Function
155+
op = preferred_size[1]
156+
op in (>, >=, <, <=) || throw(ArgumentError("the compare operation must be one of `>`, `>=`, `<` and `<=`."))
157+
op, preferred_size[2]
158+
else
159+
>=, preferred_size
160+
end
161+
if op in (>, >=)
162+
idx = findfirst(x->all(op.(x .* out_size, preferred_size)), _allowed_scale_ratios)
163+
if isnothing(idx)
164+
@warn "Failed to infer appropriate scale ratio, use `scale_ratio=2` instead." actual_size=out_size preferred_size
165+
idx = length(_allowed_scale_ratios)
166+
end
167+
elseif op in (<, <=)
168+
idx = findlast(x->all(op.(x .* out_size, preferred_size)), _allowed_scale_ratios)
169+
if isnothing(idx)
170+
@warn "Failed to infer appropriate scale ratio, use `scale_ratio=1/8` instead." actual_size=out_size preferred_size
171+
idx = 1
172+
end
173+
end
174+
return _allowed_scale_ratios[idx]
175+
end
131176

132177
function _default_out_color_space(filename::AbstractString)
133178
_jpeg_check_bytes(filename)

test/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
55
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
66
ImageIO = "82e4d734-157c-48bb-816b-45c225c6df19"
77
ImageQualityIndexes = "2996bd0c-7a13-11e9-2da2-2f5ce47296a9"
8+
Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
89
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
910
TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990"

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ using Documenter
66
using TestImages
77
using ImageQualityIndexes
88
using ImageCore
9+
using Suppressor
910

1011
# ensure TestImages artifacts are downloaded before running documenter test
1112
testimage("cameraman")

test/tst_decode.jl

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,38 @@
5151
@test size(data) == (128, 192) != 0.3 .* size(img_rgb)
5252
end
5353

54+
@testset "preferred_size" begin
55+
# lighthouse: (512, 768)
56+
actual_size = size(img_rgb)
57+
data = jpeg_decode(img_rgb_bytes; scale_ratio=0.25)
58+
data_new = jpeg_decode(img_rgb_bytes; preferred_size=size(data))
59+
@test size(data_new) == size(data)
60+
61+
data_new = jpeg_decode(img_rgb_bytes; preferred_size=(>=, size(data)))
62+
@test size(data_new) == size(data)
63+
64+
data_new = jpeg_decode(img_rgb_bytes; preferred_size=(>, size(data)))
65+
@test size(data_new) == 3/8 .* actual_size
66+
67+
data_new = jpeg_decode(img_rgb_bytes; preferred_size=(<=, size(data)))
68+
@test size(data_new) == size(data)
69+
70+
data_new = jpeg_decode(img_rgb_bytes; preferred_size=(<, size(data)))
71+
@test size(data_new) == 1/8 .* actual_size
72+
73+
data_new = @suppress_err jpeg_decode(img_rgb_bytes; preferred_size=3 .* actual_size)
74+
@test size(data_new) == 2 .* actual_size
75+
msg = @capture_err jpeg_decode(img_rgb_bytes; preferred_size=3 .* actual_size)
76+
@test occursin("Warning: Failed to infer appropriate scale ratio, use `scale_ratio=2` instead.", msg)
77+
78+
data_new = @suppress_err jpeg_decode(img_rgb_bytes; preferred_size=(<=, 1/16 .* actual_size))
79+
@test size(data_new) == 1/8 .* actual_size
80+
msg = @capture_err jpeg_decode(img_rgb_bytes; preferred_size=(<=, 1/16 .* actual_size))
81+
@test occursin("Warning: Failed to infer appropriate scale ratio, use `scale_ratio=1/8` instead.", msg)
82+
83+
@test_throws ArgumentError jpeg_decode(img_rgb_bytes; preferred_size=size(data), scale_ratio=1)
84+
end
85+
5486
@testset "transpose" begin
5587
data = jpeg_decode(jpeg_encode(img_rgb; transpose=true); transpose=true)
5688
@test assess_psnr(data, img_rgb) > 33.95

0 commit comments

Comments
 (0)