Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion src/io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,18 @@ end

### Write ##########################################################################################

_dpi_to_ppm(::Nothing) = nothing
function _dpi_to_ppm(dpi::Real)
@assert dpi > 0
pixels_per_meter = round(UInt32, dpi / 0.0254)
return (pixels_per_meter, pixels_per_meter)
end
function _dpi_to_ppm(dpi::Tuple{<:Real,<:Real})
@assert dpi[1] > 0
@assert dpi[2] > 0
return round.(UInt32, dpi ./ 0.0254)
end

const SupportedPaletteColor = Union{
AbstractRGB{<:Union{N0f8,AbstractFloat}},
TransparentRGB{T,<:Union{N0f8,AbstractFloat}} where T,
Expand Down Expand Up @@ -328,6 +340,9 @@ Write out a julia `Array` as a PNG image.
- `background`: optional background color to be stored in the `bKGD` chunk. Only meaningful for transparent images.
Valid values are `nothing` for no background, `UInt8` as a palette index for palleted images,
`Gray` for grayscale images and `RGB` for true color images.
- `dpi`: stores the pixel density given in dots per inch into the `pHYs` chunk as pixels per meter.
The density is given as `dpi` for ease of use as pixels per meter is an uncommon format. If set to `nothing`,
no pixel density is written. If set to a 2-element tuple, a different density is written for x and y, respectively.
Comment on lines +343 to +345
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pixels per meter seems to be native to PNG (cf. https://www.w3.org/TR/PNG-Chunks.html), and it is using an SI unit, so even though DPI may be more common, why not at least support both a pixels_per_meter and a dots_per_inch keyword?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would also be possible, I just don't think anybody will really use that :)


# Returns
- `nothing`
Expand All @@ -340,6 +355,7 @@ function save(
filters::Integer = Int(PNG_FILTER_PAETH),
file_gamma::Union{Nothing,Float64} = nothing,
background::Union{Nothing,UInt8,AbstractGray,AbstractRGB} = nothing,
dpi::Union{Nothing,Real,Tuple{<:Real,<:Real}} = nothing,
) where {
T,
S<:Union{AbstractMatrix{T},AbstractArray{T,3}}
Expand All @@ -363,7 +379,8 @@ function save(
compression_strategy=compression_strategy,
filters=filters,
file_gamma=file_gamma,
background=background
background=background,
pixels_per_meter = _dpi_to_ppm(dpi),
)

close_png(fp)
Expand All @@ -377,6 +394,7 @@ function save(
filters::Integer = Int(PNG_FILTER_PAETH),
file_gamma::Union{Nothing,Float64} = nothing,
background::Union{Nothing,UInt8,AbstractGray,AbstractRGB} = nothing,
dpi::Union{Nothing,Real,Tuple{<:Real,<:Real}} = nothing,
) where {
S<:Union{AbstractMatrix,AbstractArray{<:Any,3}}
}
Expand All @@ -399,6 +417,7 @@ function save(
filters=filters,
file_gamma=file_gamma,
background=background,
pixels_per_meter = _dpi_to_ppm(dpi),
)
end
end
Expand All @@ -411,6 +430,7 @@ function _save(png_ptr, info_ptr, image::S;
filters::Integer = Int(PNG_FILTER_PAETH),
file_gamma::Union{Nothing,Float64} = nothing,
background::Union{Nothing,UInt8,AbstractGray,AbstractRGB} = nothing,
pixels_per_meter::Union{Nothing,Tuple{UInt32,UInt32}} = nothing,
) where {
T,
S<:Union{AbstractMatrix{T},AbstractArray{T,3}}
Expand All @@ -426,6 +446,9 @@ function _save(png_ptr, info_ptr, image::S;
png_set_compression_level(png_ptr, compression_level)
png_set_compression_strategy(png_ptr, compression_strategy)
png_set_compression_window_bits(png_ptr, min(15, max(8, _nextpow2exp(approx_bytes))))
if pixels_per_meter !== nothing
png_set_pHYs(png_ptr, info_ptr, pixels_per_meter..., PNG_RESOLUTION_METER)
end

if color_type == PNG_COLOR_TYPE_PALETTE
# TODO: 1, 2, 4 bit-depth indices for palleted
Expand Down
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ ensure_imagemagick()
include("test_images_with_background.jl")
include("test_io.jl")
include("test_various_array_types.jl")
include("test_dpi.jl")
end

# Cleanup
Expand Down
37 changes: 37 additions & 0 deletions test/test_dpi.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@testset "dpi" begin
io_none = IOBuffer()
io_300 = IOBuffer()
io_300_300 = IOBuffer()
io_300_500 = IOBuffer()

img = rand(RGB{N0f8}, 2, 2)

PNGFiles.save(io_none, img; dpi = nothing)
PNGFiles.save(io_300, img; dpi = 300)
PNGFiles.save(io_300_300, img; dpi = (300f0, 300.0))
PNGFiles.save(io_300_500, img; dpi = (300, 500))

for io in [io_none, io_300, io_300_300, io_300_500]
seekstart(io)
@test PNGFiles.load(io) == img
end

s_none = String(take!(io_none))
Comment thread
timholy marked this conversation as resolved.
s_300 = String(take!(io_300))
s_300_300 = String(take!(io_300_300))
s_300_500 = String(take!(io_300_500))

function physblock(xdpi, ydpi)
io = IOBuffer()
write(io, "pHYs")
write(io, hton(round(UInt32, xdpi / 0.0254)))
write(io, hton(round(UInt32, ydpi / 0.0254)))
write(io, hton(UInt8(1)))
return String(take!(io))
end

@test !occursin("pHYs", s_none)
@test occursin(physblock(300, 300), s_300)
@test occursin(physblock(300, 300), s_300_300)
@test occursin(physblock(300, 500), s_300_500)
end