|
| 1 | +""" |
| 2 | + jpeg_encode(filename::AbstractString, img; kwargs...) -> Int |
| 3 | + jpeg_encode(io::IO, img; kwargs...) -> Int |
| 4 | + jpeg_encode(img; kwargs...) -> Vector{UInt8} |
| 5 | +
|
| 6 | +Encode 2D image `img` as JPEG byte sequences and write to given I/O stream or file. The |
| 7 | +return value is number of bytes. If output is not specified, the encoded result is stored |
| 8 | +in memory as return value. |
| 9 | +
|
| 10 | +# Parameters |
| 11 | +
|
| 12 | +- `transpose::Bool`: whether we need to permute the image's width and height dimension |
| 13 | + before encoding. The default value is `false`. |
| 14 | +- `quality::Int`: Constructs JPEG quantization tables appropriate for the indicated |
| 15 | + quality setting. The quality value is expressed on the 0..100 scale recommended by IJG. |
| 16 | +
|
| 17 | +!!! info "Custom compression parameters" |
| 18 | + JPEG has a large number of compression parameters that determine how the image is |
| 19 | + encoded. Most applications don't need or want to know about all these parameters. For |
| 20 | + more detailed information and explaination, please refer to the "Compression parameter |
| 21 | + selection" in [1]. Unsupported custom parameters might cause Julia segmentation fault. |
| 22 | +
|
| 23 | +# Examples |
| 24 | +
|
| 25 | +```jldoctest |
| 26 | +julia> using JpegTurbo, TestImages |
| 27 | +
|
| 28 | +julia> img = testimage("cameraman"); |
| 29 | +
|
| 30 | +julia> jpeg_encode("out.jpg", img) # write to file |
| 31 | +28210 |
| 32 | +
|
| 33 | +julia> buf = jpeg_encode(img); length(buf) # directly write to memory |
| 34 | +28210 |
| 35 | +``` |
| 36 | +
|
| 37 | +# References |
| 38 | +
|
| 39 | +- [1] [libjpeg API Documentation (libjpeg.txt)](https://raw.githubusercontent.com/libjpeg-turbo/libjpeg-turbo/main/libjpeg.txt) |
| 40 | +""" |
| 41 | +function jpeg_encode(img::AbstractMatrix; transpose=false, kwargs...) |
| 42 | + # quantilized into 8bit sequences first |
| 43 | + AT = Array{n0f8(eltype(img)), ndims(img)} |
| 44 | + # jpegturbo is a C library and assumes row-major memory order, thus `collect` the data into |
| 45 | + # contiguous memeory layout already makes a transpose. |
| 46 | + img = transpose ? convert(AT, img) : convert(AT, PermutedDimsArray(img, (2, 1))) |
| 47 | + |
| 48 | + return _encode(img; kwargs...) |
| 49 | +end |
| 50 | + |
| 51 | +function jpeg_encode(filename::AbstractString, img; kwargs...) |
| 52 | + open(filename, "w") do io |
| 53 | + jpeg_encode(io, img; kwargs...) |
| 54 | + end |
| 55 | +end |
| 56 | +# TODO(johnnychen94): further improve the performance via asynchronously IO and buffer reuse. |
| 57 | +jpeg_encode(io::IO, img; kwargs...) = write(io, jpeg_encode(img; kwargs...)) |
| 58 | + |
| 59 | + |
| 60 | +function _encode( |
| 61 | + img::Matrix{<:Colorant}; |
| 62 | + colorspace::Union{Nothing,Type} = nothing, |
| 63 | + quality::Union{Nothing,Int} = nothing, |
| 64 | + arith_code::Union{Nothing,Bool} = nothing, |
| 65 | + optimize_coding::Union{Nothing,Bool} = nothing, |
| 66 | + smoothing_factor::Union{Nothing,Int} = nothing, |
| 67 | + write_JFIF_header::Union{Nothing,Bool} = nothing, |
| 68 | + JFIF_version::Union{Nothing,VersionNumber} = nothing, |
| 69 | + density_unit::Union{Nothing,Int} = nothing, |
| 70 | + X_density::Union{Nothing,Int} = nothing, |
| 71 | + Y_density::Union{Nothing,Int} = nothing, |
| 72 | + write_Adobe_marker::Union{Nothing,Bool} = nothing |
| 73 | +) |
| 74 | + cinfo = LibJpeg.jpeg_compress_struct() |
| 75 | + cinfo_ref = Ref(cinfo) |
| 76 | + jerr = Ref{LibJpeg.jpeg_error_mgr}() |
| 77 | + cinfo.err = LibJpeg.jpeg_std_error(jerr) |
| 78 | + LibJpeg.jpeg_create_compress(cinfo_ref) |
| 79 | + |
| 80 | + # set input image information |
| 81 | + cinfo.image_width = size(img, 1) |
| 82 | + cinfo.image_height = size(img, 2) |
| 83 | + cinfo.input_components = jpeg_components(img) |
| 84 | + cinfo.in_color_space = jpeg_color_space(img) |
| 85 | + |
| 86 | + # set compression keywords |
| 87 | + # it's recommended to call `jpeg_set_defaults` first before setting custom parameters |
| 88 | + # as it's more likely to provide a working parameters and is more likely to be working |
| 89 | + # correctly in the future. |
| 90 | + LibJpeg.jpeg_set_defaults(cinfo_ref) |
| 91 | + isnothing(colorspace) || LibJpeg.jpeg_set_colorspace(cinfo_ref, jpeg_color_space(colorspace)) |
| 92 | + isnothing(quality) || LibJpeg.jpeg_set_quality(cinfo_ref, quality, true) |
| 93 | + isnothing(arith_code) || (cinfo.arith_code = arith_code) |
| 94 | + isnothing(optimize_coding) || (cinfo.optimize_coding = optimize_coding) |
| 95 | + isnothing(smoothing_factor) || (cinfo.smoothing_factor = smoothing_factor) |
| 96 | + isnothing(write_JFIF_header) || (cinfo.write_JFIF_header = write_JFIF_header) |
| 97 | + if !isnothing(JFIF_version) |
| 98 | + cinfo.JFIF_major_version = UInt8(JFIF_version.major) |
| 99 | + cinfo.JFIF_minor_version = UInt8(JFIF_version.minor) |
| 100 | + end |
| 101 | + isnothing(density_unit) || (cinfo.density_unit = density_unit) |
| 102 | + isnothing(X_density) || (cinfo.X_density = X_density) |
| 103 | + isnothing(Y_density) || (cinfo.Y_density = Y_density) |
| 104 | + isnothing(write_Adobe_marker) || (cinfo.write_Adobe_marker = write_Adobe_marker) |
| 105 | + |
| 106 | + # set destination |
| 107 | + # TODO(johnnychen94): allow pre-allocated buffer |
| 108 | + bufsize = Ref{Culong}(0) |
| 109 | + buf_ptr = Ref{Ptr{UInt8}}(C_NULL) |
| 110 | + LibJpeg.jpeg_mem_dest(cinfo_ref, buf_ptr, bufsize) |
| 111 | + |
| 112 | + # compression stage |
| 113 | + LibJpeg.jpeg_start_compress(cinfo_ref, true) |
| 114 | + row_stride = size(img, 1) * jpeg_components(img) |
| 115 | + row_pointer = Ref{Ptr{UInt8}}(0) |
| 116 | + while (cinfo.next_scanline < cinfo.image_height) |
| 117 | + row_pointer[] = pointer(img) + cinfo.next_scanline * row_stride |
| 118 | + LibJpeg.jpeg_write_scanlines(cinfo_ref, row_pointer, 1); |
| 119 | + end |
| 120 | + LibJpeg.jpeg_finish_compress(cinfo_ref) |
| 121 | + LibJpeg.jpeg_destroy_compress(cinfo_ref) |
| 122 | + |
| 123 | + return unsafe_wrap(Array, buf_ptr[], bufsize[]; own=true) |
| 124 | +end |
0 commit comments