1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
use std::ffi::CStr;

/// Pixel format determines the layout of pixels in memory.
#[doc(alias = "TJPF")]
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(i32)]
pub enum PixelFormat {
    /// RGB pixel format.
    ///
    /// The red, green, and blue components in the image are stored in 3-byte pixels in the order
    /// R, G, B from lowest to highest byte address within each pixel.
    #[doc(alias = "TJPF_RGB")]
    RGB = sys::TJPF_TJPF_RGB,

    /// BGR pixel format.
    ///
    /// The red, green, and blue components in the image are stored in 3-byte pixels in the order
    /// B, G, R from lowest to highest byte address within each pixel.
    #[doc(alias = "TJPF_BGR")]
    BGR = sys::TJPF_TJPF_BGR,

    /// RGBX pixel format.
    ///
    /// The red, green, and blue components in the image are stored in 4-byte pixels in the order
    /// R, G, B from lowest to highest byte address within each pixel. The X component is ignored
    /// when compressing and undefined when decompressing.
    #[doc(alias = "TJPF_RGBX")]
    RGBX = sys::TJPF_TJPF_RGBX,

    /// BGRX pixel format.
    ///
    /// The red, green, and blue components in the image are stored in 4-byte pixels in the order
    /// B, G, R from lowest to highest byte address within each pixel. The X component is ignored
    /// when compressing and undefined when decompressing.
    #[doc(alias = "TJPF_BGRX")]
    BGRX = sys::TJPF_TJPF_BGRX,

    /// XBGR pixel format.
    ///
    /// The red, green, and blue components in the image are stored in 4-byte pixels in the order
    /// R, G, B from highest to lowest byte address within each pixel. The X component is ignored
    /// when compressing and undefined when decompressing.
    #[doc(alias = "TJPF_XBGR")]
    XBGR = sys::TJPF_TJPF_XBGR,

    /// XRGB pixel format.
    ///
    /// The red, green, and blue components in the image are stored in 4-byte pixels in the order
    /// B, G, R from highest to lowest byte address within each pixel. The X component is ignored
    /// when compressing and undefined when decompressing.
    #[doc(alias = "TJPF_XRGB")]
    XRGB = sys::TJPF_TJPF_XRGB,

    /// Grayscale pixel format.
    ///
    /// Each 1-byte pixel represents a luminance (brightness) level from 0 to 255.
    #[doc(alias = "TJPF_GRAY")]
    GRAY = sys::TJPF_TJPF_GRAY,

    /// RGBA pixel format.
    ///
    /// This is the same as [`PixelFormat::RGBX`], except that when decompressing, the X component
    /// is guaranteed to be 0xFF, which can be interpreted as an opaque alpha channel.
    #[doc(alias = "TJPF_RGBA")]
    RGBA = sys::TJPF_TJPF_RGBA,

    /// BGRA pixel format.
    ///
    /// This is the same as [`PixelFormat::BGRX`], except that when decompressing, the X component
    /// is guaranteed to be 0xFF, which can be interpreted as an opaque alpha channel.
    #[doc(alias = "TJPF_BGRA")]
    BGRA = sys::TJPF_TJPF_BGRA,

    /// ABGR pixel format.
    ///
    /// This is the same as [`PixelFormat::XBGR`], except that when decompressing, the X component
    /// is guaranteed to be 0xFF, which can be interpreted as an opaque alpha channel.
    #[doc(alias = "TJPF_ABGR")]
    ABGR = sys::TJPF_TJPF_ABGR,

    /// ARGB pixel format.
    ///
    /// This is the same as [`PixelFormat::ARGB`], except that when decompressing, the X component
    /// is guaranteed to be 0xFF, which can be interpreted as an opaque alpha channel.
    #[doc(alias = "TJPF_ARGB")]
    ARGB = sys::TJPF_TJPF_ARGB,

    /// CMYK pixel format.
    ///
    /// Unlike RGB, which is an additive color model used primarily for display, CMYK
    /// (Cyan/Magenta/Yellow/Key) is a subtractive color model used primarily for printing. In the
    /// CMYK color model, the value of each color component typically corresponds to an amount of
    /// cyan, magenta, yellow, or black ink that is applied to a white background. In order to
    /// convert between CMYK and RGB, it is necessary to use a color management system (CMS). A CMS
    /// will attempt to map colors within the printer's gamut to perceptually similar colors in the
    /// display's gamut and vice versa, but the mapping is typically not 1:1 or reversible, nor can
    /// it be defined with a simple formula. Thus, such a conversion is out of scope for a codec
    /// library. However, the TurboJPEG API allows for compressing CMYK pixels into a YCCK JPEG
    /// image (see TJCS_YCCK) and decompressing YCCK JPEG images into CMYK pixels.
    #[doc(alias = "TJPF_CMYK")]
    CMYK = sys::TJPF_TJPF_CMYK,
}

impl PixelFormat {
    /// The size of a pixel in bytes.
    pub fn size(&self) -> usize {
        match self {
            PixelFormat::RGB => 3,
            PixelFormat::BGR => 3,
            PixelFormat::RGBX => 4,
            PixelFormat::BGRX => 4,
            PixelFormat::XBGR => 4,
            PixelFormat::XRGB => 4,
            PixelFormat::GRAY => 1,
            PixelFormat::RGBA => 4,
            PixelFormat::BGRA => 4,
            PixelFormat::ABGR => 4,
            PixelFormat::ARGB => 4,
            PixelFormat::CMYK => 4,
        }
    }

    pub fn from_i32(format: i32) -> Result<PixelFormat> {
        Ok(match format {
            sys::TJPF_TJPF_RGB => PixelFormat::RGB,
            sys::TJPF_TJPF_BGR => PixelFormat::BGR,
            sys::TJPF_TJPF_RGBX => PixelFormat::RGBX,
            sys::TJPF_TJPF_BGRX => PixelFormat::BGRX,
            sys::TJPF_TJPF_XBGR => PixelFormat::XBGR,
            sys::TJPF_TJPF_XRGB => PixelFormat::XRGB,
            sys::TJPF_TJPF_GRAY => PixelFormat::GRAY,
            sys::TJPF_TJPF_RGBA => PixelFormat::RGBA,
            sys::TJPF_TJPF_BGRA => PixelFormat::BGRA,
            sys::TJPF_TJPF_ABGR => PixelFormat::ABGR,
            sys::TJPF_TJPF_ARGB => PixelFormat::ARGB,
            sys::TJPF_TJPF_CMYK => PixelFormat::CMYK,
            other => return Err(Error::BadPixelFormat(other)),
        })
    }
}


/// Chrominance subsampling options.
///
/// When pixels are converted from RGB to YCbCr or from CMYK to YCCK as part of the JPEG
/// compression process, some of the Cb and Cr (chrominance) components can be discarded or
/// averaged together to produce a smaller image with little perceptible loss of image clarity (the
/// human eye is more sensitive to small changes in brightness than to small changes in color).
/// This is called "chrominance subsampling".
#[doc(alias = "TJSAMP")]
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u32)]
pub enum Subsamp {
    /// No chrominance subsampling (4:4:4);
    ///
    /// The JPEG or YUV image will contain one chrominance component for every pixel in the source
    /// image.
    #[doc(alias = "TJSAMP_444")]
    None = sys::TJSAMP_TJSAMP_444,

    /// 2x1 chrominance subsampling (4:2:2).
    ///
    /// The JPEG or YUV image will contain one chrominance component for every 2x1 block of pixels
    /// in the source image.
    #[doc(alias = "TJSAMP_422")]
    Sub2x1 = sys::TJSAMP_TJSAMP_422,

    /// 2x2 chrominance subsampling (4:2:0).
    ///
    /// The JPEG or YUV image will contain one chrominance component for every 2x2 block of pixels
    /// in the source image.
    #[doc(alias = "TJSAMP_420")]
    Sub2x2 = sys::TJSAMP_TJSAMP_420,

    /// Grayscale.
    ///
    /// The JPEG or YUV image will contain no chrominance components.
    #[doc(alias = "TJSAMP_GRAY")]
    Gray = sys::TJSAMP_TJSAMP_GRAY,

    /// 1x2 chrominance subsampling (4:4:0).
    ///
    /// The JPEG or YUV image will contain one chrominance component for every 1x2 block of pixels
    /// in the source image.
    ///
    /// # Note
    ///
    /// 4:4:0 subsampling is not fully accelerated in libjpeg-turbo.
    #[doc(alias = "TJSAMP_440")]
    Sub1x2 = sys::TJSAMP_TJSAMP_440,

    /// 4x1 chrominance subsampling (4:1:1).
    ///
    /// The JPEG or YUV image will contain one chrominance component for every 4x1 block of pixels
    /// in the source image. JPEG images compressed with 4:1:1 subsampling will be almost exactly
    /// the same size as those compressed with 4:2:0 subsampling, and in the aggregate, both
    /// subsampling methods produce approximately the same perceptual quality. However, 4:1:1 is
    /// better able to reproduce sharp horizontal features.
    ///
    /// # Note
    ///
    /// 4:1:1 subsampling is not fully accelerated in libjpeg-turbo.
    #[doc(alias = "TJSAMP_411")]
    Sub4x1 = sys::TJSAMP_TJSAMP_411,
}

impl Subsamp {
    pub fn from_u32(subsamp: u32) -> Result<Subsamp> {
        Ok(match subsamp {
            sys::TJSAMP_TJSAMP_444 => Subsamp::None,
            sys::TJSAMP_TJSAMP_422 => Subsamp::Sub2x1,
            sys::TJSAMP_TJSAMP_420 => Subsamp::Sub2x2,
            sys::TJSAMP_TJSAMP_GRAY => Subsamp::Gray,
            sys::TJSAMP_TJSAMP_440 => Subsamp::Sub1x2,
            sys::TJSAMP_TJSAMP_411 => Subsamp::Sub4x1,
            other => return Err(Error::BadSubsamp(other)),
        })
    }
}


/// JPEG colorspaces.
#[doc(alias = "TJCS")]
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u32)]
pub enum Colorspace {
    /// RGB colorspace.
    ///
    /// When compressing the JPEG image, the R, G, and B components in the source image are
    /// reordered into image planes, but no colorspace conversion or subsampling is performed. RGB
    /// JPEG images can be decompressed to any of the extended RGB pixel formats or grayscale, but
    /// they cannot be decompressed to YUV images.
    #[doc(alias = "TJCS_RGB")]
    RGB = sys::TJCS_TJCS_RGB,

    /// YCbCr colorspace.
    ///
    /// YCbCr is not an absolute colorspace but rather a mathematical transformation of RGB
    /// designed solely for storage and transmission. YCbCr images must be converted to RGB before
    /// they can actually be displayed. In the YCbCr colorspace, the Y (luminance) component
    /// represents the black & white portion of the original image, and the Cb and Cr (chrominance)
    /// components represent the color portion of the original image. Originally, the analog
    /// equivalent of this transformation allowed the same signal to drive both black & white and
    /// color televisions, but JPEG images use YCbCr primarily because it allows the color data to
    /// be optionally subsampled for the purposes of reducing bandwidth or disk space. YCbCr is the
    /// most common JPEG colorspace, and YCbCr JPEG images can be compressed from and decompressed
    /// to any of the extended RGB pixel formats or grayscale, or they can be decompressed to YUV
    /// planar images.
    #[doc(alias = "TJCS_YCbCr")]
    YCbCr = sys::TJCS_TJCS_YCbCr,

    /// Grayscale colorspace.
    ///
    /// The JPEG image retains only the luminance data (Y component), and any color data from the
    /// source image is discarded. Grayscale JPEG images can be compressed from and decompressed to
    /// any of the extended RGB pixel formats or grayscale, or they can be decompressed to YUV
    /// planar images.
    #[doc(alias = "TJCS_GRAY")]
    Gray = sys::TJCS_TJCS_GRAY,

    /// CMYK colorspace.
    ///
    /// When compressing the JPEG image, the C, M, Y, and K components in the source image are
    /// reordered into image planes, but no colorspace conversion or subsampling is performed. CMYK
    /// JPEG images can only be decompressed to CMYK pixels.
    #[doc(alias = "TJCS_CMYK")]
    CMYK = sys::TJCS_TJCS_CMYK,

    /// YCCK colorspace.
    ///
    /// YCCK (AKA "YCbCrK") is not an absolute colorspace but rather a mathematical transformation
    /// of CMYK designed solely for storage and transmission. It is to CMYK as YCbCr is to RGB.
    /// CMYK pixels can be reversibly transformed into YCCK, and as with YCbCr, the chrominance
    /// components in the YCCK pixels can be subsampled without incurring major perceptual loss.
    /// YCCK JPEG images can only be compressed from and decompressed to CMYK pixels.
    #[doc(alias = "TJCS_YCCK")]
    YCCK = sys::TJCS_TJCS_YCCK,
}

impl Colorspace {
    pub fn from_u32(colorspace: u32) -> Result<Colorspace> {
        Ok(match colorspace {
            sys::TJCS_TJCS_RGB => Colorspace::RGB,
            sys::TJCS_TJCS_YCbCr => Colorspace::YCbCr,
            sys::TJCS_TJCS_GRAY => Colorspace::Gray,
            sys::TJCS_TJCS_CMYK => Colorspace::CMYK,
            sys::TJCS_TJCS_YCCK => Colorspace::YCCK,
            other => return Err(Error::BadColorspace(other)),
        })
    }
}


/// Specialized `Result` type for TurboJPEG.
pub type Result<T> = std::result::Result<T, Error>;

/// An error that can occur in TurboJPEG.
#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("TurboJPEG error: {0}")]
    TurboJpegError(String),
    #[error("TurboJPEG returned null pointer")]
    Null(),
    #[error("TurboJPEG returned unknown pixel format: {0}")]
    BadPixelFormat(i32),
    #[error("TurboJPEG returned unknown subsampling option: {0}")]
    BadSubsamp(u32),
    #[error("TurboJPEG returned unknown colorspace: {0}")]
    BadColorspace(u32),
    #[error("integer value {0:?} overflowed")]
    IntegerOverflow(&'static str),
}

pub(crate) unsafe fn get_error(handle: sys::tjhandle) -> Error {
    let msg = CStr::from_ptr(sys::tjGetErrorStr2(handle));
    Error::TurboJpegError(msg.to_string_lossy().into_owned())
}