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 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
use std::ptr;
use std::convert::TryInto as _;
use crate::buf::{OwnedBuf, OutputBuf};
use crate::common::{Error, Result};
use crate::handle::Handle;
/// Transforms JPEG images without recompression.
///
/// TurboJPEG applies the transformation on the DCT coefficients, without performing complete
/// decompression. This is faster and also means that the transforms are lossless.
#[derive(Debug)]
#[doc(alias = "tjhandle")]
pub struct Transformer {
handle: Handle,
}
/// Lossless transform of a JPEG image.
///
/// When constructing an instance, you may start from the default transform
/// ([`Transform::default()`][Self::default]) or from an operation ([`Transform::op()`]) and modify
/// only the fields that you need.
///
/// # Examples
///
/// Rotate image clockwise by 90 degrees:
///
/// ```
/// # use turbojpeg::{Transform, TransformOp};
/// let transform = Transform::op(TransformOp::Rot90);
/// ```
///
/// Rotate image counterclockwise by 90 degrees and fail if the transform is not
/// [perfect][Self::perfect]:
///
/// ```
/// # use turbojpeg::{Transform, TransformOp};
/// let mut transform = Transform::op(TransformOp::Rot270);
/// transform.perfect = true;
/// ```
///
/// Flip image vertically and [trim][Self::trim] the image on the right edge if the transform is
/// imperfect:
///
/// ```
/// # use turbojpeg::{Transform, TransformOp};
/// let mut transform = Transform::op(TransformOp::Vflip);
/// transform.trim = true;
/// ```
///
/// Crop image to size (200, 100) starting at pixel (16, 32), without applying any transform:
///
/// ```
/// # use turbojpeg::{Transform, TransformOp, TransformCrop};
/// let mut transform = Transform::default();
/// transform.crop = Some(TransformCrop { x: 16, y: 32, width: Some(200), height: Some(100) });
/// ```
#[derive(Debug, Default, Clone)]
#[doc(alias = "tjtransform")]
#[non_exhaustive]
pub struct Transform {
/// Transform operation that is applied.
pub op: TransformOp,
/// Crop the input image before applying the transform.
#[doc(alias = "TJXOPT_CROP")]
pub crop: Option<TransformCrop>,
/// Return an error if the transform is not perfect.
///
/// Lossless transforms operate on MCU blocks, whose size depends on the level of chrominance
/// subsampling used (see [`Subsamp::mcu_width()`][crate::Subsamp::mcu_width] and
/// [`Subsamp::mcu_height()`][crate::Subsamp::mcu_height]). If the image width or height is not
/// evenly divisible by the MCU block size, then there will be partial MCU blocks on the right
/// and bottom edges. It is not possible to move these partial MCU blocks to the top or left of
/// the image, so any transform that would require that is "imperfect".
///
/// If this option is not specified and [`trim`][Self::trim] is not enabled, then any partial
/// MCU blocks that cannot be transformed will be left in place, which will create odd-looking
/// strips on the right or bottom edge of the image.
#[doc(alias = "TJXOPT_PERFECT")]
pub perfect: bool,
/// Discard any partial MCU blocks that cannot be transformed.
#[doc(alias = "TJXOPT_TRIM")]
pub trim: bool,
/// Discard the color data in the input image and produce a grayscale output image.
#[doc(alias = "TJXOPT_GRAY")]
pub gray: bool,
/// Enable progressive entropy coding in the output image generated by this particular
/// transform.
///
/// Progressive entropy coding will generally improve compression relative to baseline entropy
/// coding (the default), but it will reduce compression and decompression performance
/// considerably.
#[doc(alias = "TJXOPT_PROGRESSIVE")]
pub progressive: bool,
/// Enable optimized baseline entropy coding in the JPEG image generated by this particular
/// transform.
///
/// Optimized baseline entropy coding will improve compression slightly (generally 5% or less.)
#[doc(alias = "TJXOPT_OPTIMIZE")]
pub optimize: bool,
/// Do not copy any extra markers (including EXIF and ICC profile data) from the input image to
/// the output image.
#[doc(alias = "TJXOPT_COPYNONE")]
pub copy_none: bool,
}
impl Transform {
/// Creates a [`Transform`] with the given `op` and all other parameters set to default.
///
/// # Example
//
/// ```
/// # use turbojpeg::{Transform, TransformOp};
/// let mut transform = Transform::op(TransformOp::Rot90);
/// transform.progressive = true;
/// ```
pub fn op(op: TransformOp) -> Transform {
Transform { op, ..Transform::default() }
}
}
/// Transform operation.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[doc(alias = "TJXOP")]
#[repr(u32)]
#[non_exhaustive]
pub enum TransformOp {
/// No transformation (noop).
#[doc(alias = "TJXOP_NONE")]
None = raw::TJXOP_TJXOP_NONE,
/// Flip (mirror) image horizontally.
///
/// This transform is imperfect if there are any partial MCU blocks on the right edge (see
/// [`Transform::perfect`].)
#[doc(alias = "TJXOP_HFLIP")]
Hflip = raw::TJXOP_TJXOP_HFLIP,
/// Flip (mirror) image vertically.
///
/// This transform is imperfect if there are any partial MCU blocks on the bottom edge (see
/// [`Transform::perfect`].)
#[doc(alias = "TJXOP_VFLIP")]
Vflip = raw::TJXOP_TJXOP_VFLIP,
/// Transpose image (flip/mirror along upper left to lower right axis).
///
/// This transform is always perfect.
#[doc(alias = "TJXOP_TRANSPOSE")]
Transpose = raw::TJXOP_TJXOP_TRANSPOSE,
/// Transverse transpose image (flip/mirror along upper right to lower left axis).
///
/// This transform is imperfect if there are any partial MCU blocks in the image (see
/// [`Transform::perfect`].)
#[doc(alias = "TJXOP_TRANSVERSE")]
Transverse = raw::TJXOP_TJXOP_TRANSVERSE,
/// Rotate image clockwise by 90 degrees.
///
/// This transform is imperfect if there are any partial MCU blocks on the bottom edge (see
/// [`Transform::perfect`].)
#[doc(alias = "TJXOP_ROT90")]
Rot90 = raw::TJXOP_TJXOP_ROT90,
/// Rotate image 180 degrees.
///
/// This transform is imperfect if there are any partial MCU blocks in the image (see
/// [`Transform::perfect`].)
#[doc(alias = "TJXOP_ROT180")]
Rot180 = raw::TJXOP_TJXOP_ROT180,
/// Rotate image counter-clockwise by 90 degrees.
///
/// This transform is imperfect if there are any partial MCU blocks on the right edge (see
/// [`Transform::perfect`].)
Rot270 = raw::TJXOP_TJXOP_ROT270,
}
impl Default for TransformOp {
fn default() -> Self {
TransformOp::None
}
}
/// Transform cropping region.
///
/// The [`x`][Self::x] and [`y`][Self::y] position of the region must be aligned on MCU boundaries.
/// The size of the MCU depends on the chrominance subsampling option, which can be obtained using
/// [`Decompressor::read_header()`][crate::Decompressor::read_header].
///
/// The default instance performs no cropping.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[doc(alias = "tjregion")]
pub struct TransformCrop {
/// Left boundary of the region. This must be divisible by the MCU width (see
/// [`Subsamp::mcu_width()`][crate::Subsamp::mcu_width]).
pub x: usize,
/// Upper boundary of the region. This must be divisible by the MCU height (see
/// [`Subsamp::mcu_height()`][crate::Subsamp::mcu_height]).
pub y: usize,
/// Width of the region. If None is given, the region ends at the right boundary of the image.
pub width: Option<usize>,
/// Height of the region. If None is given, the region ends at the bottom boundary of the
/// image.
pub height: Option<usize>,
}
impl Transformer {
/// Create a new transformer instance.
#[doc(alias = "tj3Init")]
pub fn new() -> Result<Transformer> {
let handle = Handle::new(raw::TJINIT_TJINIT_TRANSFORM)?;
Ok(Self { handle })
}
/// Apply a transformation to the compressed JPEG.
///
/// This is the main transformation method, which gives you full control of the output buffer. If
/// you don't need this level of control, you can use one of the convenience wrappers below.
///
/// # Example
///
/// ```
/// // read JPEG data from file
/// let jpeg_data = std::fs::read("examples/parrots.jpg")?;
///
/// // initialize the transformer
/// let mut transformer = turbojpeg::Transformer::new()?;
///
/// // define the transformation: flip vertically, trim partial MCU blocks on the bottom edge
/// let mut transform = turbojpeg::Transform::op(turbojpeg::TransformOp::Vflip);
/// transform.trim = true;
///
/// // initialize the output buffer
/// let mut flipped_data = turbojpeg::OutputBuf::new_owned();
///
/// // apply the transformation
/// transformer.transform(&transform, &jpeg_data, &mut flipped_data)?;
///
/// // write the flipped JPEG back to disk
/// std::fs::write(std::env::temp_dir().join("flipped_parrots.jpg"), &flipped_data)?;
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[doc(alias = "tj3Transform")]
pub fn transform(
&mut self,
transform: &Transform,
jpeg_data: &[u8],
output: &mut OutputBuf,
) -> Result<()> {
let mut options = 0;
if transform.perfect { options |= raw::TJXOPT_PERFECT }
if transform.trim { options |= raw::TJXOPT_TRIM }
if transform.gray { options |= raw::TJXOPT_GRAY }
if transform.progressive { options |= raw::TJXOPT_PROGRESSIVE }
if transform.optimize { options |= raw::TJXOPT_OPTIMIZE }
if transform.copy_none { options |= raw::TJXOPT_COPYNONE }
let mut region = raw::tjregion {
x: 0, y: 0,
w: 0, h: 0,
};
if let Some(crop) = transform.crop {
region.x = crop.x.try_into().map_err(|_| Error::IntegerOverflow("crop.x"))?;
region.y = crop.y.try_into().map_err(|_| Error::IntegerOverflow("crop.y"))?;
if let Some(crop_w) = crop.width {
region.w = crop_w.try_into().map_err(|_| Error::IntegerOverflow("crop.width"))?;
}
if let Some(crop_h) = crop.height {
region.h = crop_h.try_into().map_err(|_| Error::IntegerOverflow("crop.height"))?;
}
options |= raw::TJXOPT_CROP;
}
let mut transform = raw::tjtransform {
r: region,
op: transform.op as libc::c_int,
options: options as libc::c_int,
data: ptr::null_mut(),
customFilter: None,
};
self.handle.set(
raw::TJPARAM_TJPARAM_NOREALLOC,
if output.is_owned { 0 } else { 1 } as libc::c_int,
)?;
let mut output_len = output.len as raw::size_t;
let res = unsafe {
raw::tj3Transform(
self.handle.as_ptr(),
jpeg_data.as_ptr(), jpeg_data.len() as raw::size_t,
1, &mut output.ptr, &mut output_len,
&mut transform,
)
};
output.len = output_len as usize;
if res != 0 {
return Err(self.handle.get_error())
} else if output.ptr.is_null() {
output.len = 0;
return Err(Error::Null)
}
Ok(())
}
/// Transforms the `image` into an owned buffer.
///
/// This method automatically allocates the memory and avoids needless copying.
pub fn transform_to_owned(&mut self, transform: &Transform, jpeg_data: &[u8]) -> Result<OwnedBuf> {
let mut buf = OutputBuf::new_owned();
self.transform(transform, jpeg_data, &mut buf)?;
Ok(buf.into_owned())
}
/// Transform the `image` into a new `Vec<u8>`.
///
/// This method copies the transformed data into a new `Vec`. If you would like to avoid the
/// extra allocation and copying, consider using
/// [`transform_to_owned()`][Self::transform_to_owned] instead.
pub fn transform_to_vec(&mut self, transform: &Transform, jpeg_data: &[u8]) -> Result<Vec<u8>> {
let mut buf = OutputBuf::new_owned();
self.transform(transform, jpeg_data, &mut buf)?;
Ok(buf.to_vec())
}
/// Transform the `image` into the slice `output`.
///
/// Returns the size of the transformed JPEG data. If the transformed image does not fit into
/// `dest`, this method returns an error.
///
/// You can use [`compressed_buf_len()`][crate::compressed_buf_len] to determine buffer size that
/// should be enough for the image, but there are some rare cases (such as transforming images
/// with a large amount of embedded EXIF or ICC profile data) in which the output image will be
/// larger than the size returned by [`compressed_buf_len()`][crate::compressed_buf_len].
pub fn transform_to_slice(
&mut self,
transform: &Transform,
jpeg_data: &[u8],
output: &mut [u8],
) -> Result<usize> {
let mut buf = OutputBuf::borrowed(output);
self.transform(transform, jpeg_data, &mut buf)?;
Ok(buf.len())
}
}
/// Losslessly transform a JPEG image without recompression.
///
/// TurboJPEG applies the transformation on the DCT coefficients, without performing complete
/// decompression. This is faster and also means that the transforms are lossless.
///
/// Returns the transformed JPEG data in a buffer owned by TurboJPEG. If this does not fit your
/// needs, please see [`Transformer`].
///
/// # Example
///
/// ```
/// // read JPEG data from file
/// let jpeg_data = std::fs::read("examples/parrots.jpg")?;
///
/// // define the transformation: rotate 90 degrees clockwise
/// let mut transform = turbojpeg::Transform::op(turbojpeg::TransformOp::Rot90);
/// transform.optimize = true;
///
/// // apply the transformation
/// let rotated_data = turbojpeg::transform(&transform, &jpeg_data)?;
///
/// // write the rotated JPEG back to disk
/// std::fs::write(std::env::temp_dir().join("rotated_parrots.jpg"), &rotated_data)?;
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn transform(transform: &Transform, jpeg_data: &[u8]) -> Result<OwnedBuf> {
let mut transformer = Transformer::new()?;
transformer.transform_to_owned(transform, jpeg_data)
}