         case faces = "faces"
         case entropy = "entropy"
     }
+
+    public enum Format: String {
+        case jpeg = "jpg"
+        case png = "png"
+        case json = "json"
+        case mp4 = "mp4"
+        case webp = "webp"
+    }
+    
+
+    // MARK: - Properties
+
+    fileprivate var storage = [ImageOptionKey: Any]()
+
+
+    // MARK: - Initializers
+
+    public init(format: Format? = nil, width: CGFloat? = nil, height: CGFloat? = nil, scale: CGFloat? = nil, fit: FitMode? = nil, crop: [CropMode]? = nil) {
+        self.format = format
+        self.width = width
+        self.height = height
+        self.scale = scale
+        self.fit = fit
+        self.crop = crop
+    }
+
+
+    // MARK: - Public
+
+    /// An array of query items that can be used in conjunction with an instance
+    /// of `URLComponents` to generate an image URL.
+    public var queryItems: [URLQueryItem] {
+        var items = [URLQueryItem]()
+
+        // Adjustment Properties
+
+        if let value = brightness {
+            items.append(URLQueryItem(name: "bri", value: String(value)))
+        }
+
+        if let value = contrast {
+            items.append(URLQueryItem(name: "con", value: String(value)))
+        }
+
+        if let value = exposure {
+            items.append(URLQueryItem(name: "exp", value: String(value)))
+        }
+
+        if let value = gamma {
+            items.append(URLQueryItem(name: "gam", value: String(value)))
+        }
+
+        if let value = highlight {
+            items.append(URLQueryItem(name: "high", value: String(value)))
+        }
+
+        if let value = hue {
+            items.append(URLQueryItem(name: "hue", value: String(value)))
+        }
+
+        if let value = invert {
+            items.append(URLQueryItem(name: "invert", value: String(value)))
+        }
+
+        if let value = saturation {
+            items.append(URLQueryItem(name: "sat", value: String(value)))
+        }
+
+        if let value = shadow {
+            items.append(URLQueryItem(name: "shad", value: String(value)))
+        }
+
+        if let value = sharpen {
+            items.append(URLQueryItem(name: "sharp", value: String(value)))
+        }
+
+        if let value = vibrance {
+            items.append(URLQueryItem(name: "vib", value: String(value)))
+        }
+
+        // Size Properties
+
+        if let value = width {
+            items.append(URLQueryItem(name: "w", value: String(describing: value)))
+        }
+
+        if let value = height {
+            items.append(URLQueryItem(name: "h", value: String(describing: value)))
+        }
+
+        if let value = fit {
+            items.append(URLQueryItem(name: "fit", value: value.rawValue))
+        }
+
+        if let value = scale {
+            items.append(URLQueryItem(name: "dpr", value: String(describing: value)))
+        }
+
+        if let value = crop {
+            items.append(URLQueryItem(name: "crop", value: value.map({ $0.rawValue }).joined(separator: ",")))
+        }
+
+        if let value = cropRect {
+            let serialized = "\(value.origin.x),\(value.origin.y),\(value.width),\(value.height)"
+            items.append(URLQueryItem(name: "rect", value: serialized))
+        }
+
+        // Format Properties
+
+        if let value = format {
+            items.append(URLQueryItem(name: "fm", value: value.rawValue))
+        }
+
+        if let value = DPI {
+            items.append(URLQueryItem(name: "dpi", value: String(value)))
+        }
+
+        if let value = lossless {
+            items.append(URLQueryItem(name: "lossless", value: String(value)))
+        }
+
+        if let value = quality {
+            items.append(URLQueryItem(name: "q", value: String(value)))
+        }
+
+        if let value = colorQuantization {
+            items.append(URLQueryItem(name: "colorquant", value: String(value)))
+        }
+
+        // Background Properties
+
+        if let value = backgroundColor, let hex = value.hexadecimalColorString {
+            items.append(URLQueryItem(name: "bg", value: hex))
+        }
+
+        // Text properties
+
+        if let value = textColor, let hexadecimalString = value.hexadecimalColorString {
+            items.append(URLQueryItem(name: "txtclr", value: hexadecimalString))
+        }
+
+        if let value = textSize {
+            items.append(URLQueryItem(name: "txtsize", value: String(describing: value)))
+        }
+
+        return items
+    }
+}
+
+// MARK: - Adjustment Options
+
+extension ImageOptions {
+
+    /// Adjusts the brightness of the image. Values are clamped to the range
+    /// `-100...100`.
+    ///
+    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-bri)
+    public var brightness: Int? {
+        get { return storage[.brightness] as? Int }
+        set { storage[.brightness] = newValue?.clamped(to: -100...100) }
+    }
+
+    /// Adjusts the contrast of the image. Values are clamped to the range
+    /// `-100...100`.
+    ///
+    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-con)
+    public var contrast: Int? {
+        get { return storage[.contrast] as? Int }
+        set { storage[.contrast] = newValue?.clamped(to: -100...100) }
+    }
+
+    /// Adjusts the exposure of the image. Values are clamped to the range
+    /// `-100...100`.
+    ///
+    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-exp)
+    public var exposure: Int? {
+        get { return storage[.exposure] as? Int }
+        set { storage[.exposure] = newValue?.clamped(to: -100...100) }
+    }
+
+    ///Adjusts gamma/midtone brightness. Values are clamped to the range
+    /// `-100...100`.
+    ///
+    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-gam)
+    public var gamma: Int? {
+        get { return storage[.gamma] as? Int }
+        set { storage[.gamma] = newValue?.clamped(to: -100...100) }
+    }
+
+    /// Adjusts the highlight tonal mapping of an image while preserving spatial
+    /// detail. Values are clamped to the range `-100...100`.
+    ///
+    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-high)
+    public var highlight: Int? {
+        get { return storage[.highlight] as? Int }
+        set { storage[.highlight] = newValue?.clamped(to: -100...100) }
+    }
+
+    /// Changes the overall hue, or tint, of the source pixels. Values are
+    /// clamped to the range `0...359`.
+    ///
+    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-hue)
+    public var hue: Int? {
+        get { return storage[.hue] as? Int }
+        set { storage[.hue] = newValue?.clamped(to: 0...359) }
+    }
+
+    /// Inverts all the pixel colors and brightness values within the image
+    /// producing a negative of the image.
+    ///
+    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-invert)
+    public var invert: Bool? {
+        get { return storage[.invert] as? Bool }
+        set { storage[.invert] = newValue }
+    }
+
+    /// Adjusts the saturation of the image. Values are clamped to the range
+    /// `-100...100`.
+    ///
+    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-sat)
+    public var saturation: Int? {
+        get { return storage[.saturation] as? Int }
+        set { storage[.saturation] = newValue?.clamped(to: -100...100) }
+    }
+
+    /// Adjusts the shadow tonal mapping of an image while preserving spatial
+    /// detail. Values are clamped to the range `-100...100`.
+    ///
+    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-shad)
+    public var shadow: Int? {
+        get { return storage[.shadow] as? Int }
+        set { storage[.shadow] = newValue?.clamped(to: -100...100) }
+    }
+
+    /// Sharpens the image details using luminance sharpening. Values are
+    /// clamped to the range `0...100`.
+    ///
+    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-sharp)
+    public var sharpen: Int? {
+        get { return storage[.sharpen] as? Int }
+        set { storage[.sharpen] = newValue?.clamped(to: 0...100) }
+    }
+
+    /// Adjusts the vibrance of an image while keeping pleasing skin tones.
+    /// Values are clamped to the range `-100...100`.
+    ///
+    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-vib)
+    public var vibrance: Int? {
+        get { return storage[.vibrance] as? Int }
+        set { storage[.vibrance] = newValue?.clamped(to: -100...100) }
+    }
+}
+
+// MARK: - Size Options
+
+extension ImageOptions {
 
     /// The width of the output image.
     ///


public struct ImageOptions {

    // MARK: - Properties

    fileprivate var storage = [ImageOptionKey: Any]()


    // MARK: - Initializers

    public init(format: Format? = nil, width: CGFloat? = nil, height: CGFloat? = nil, scale: CGFloat? = nil, fit: FitMode? = nil, crop: [CropMode]? = nil) {
        self.format = format
        self.width = width
        self.height = height
        self.scale = scale
        self.fit = fit
        self.crop = crop
    }


    // MARK: - Public

    /// An array of query items that can be used in conjunction with an instance
    /// of `URLComponents` to generate an image URL.
    public var queryItems: [URLQueryItem] {
        var items = [URLQueryItem]()

        // Adjustment Properties

        if let value = brightness {
            items.append(URLQueryItem(name: "bri", value: String(value)))
        }

        if let value = contrast {
            items.append(URLQueryItem(name: "con", value: String(value)))
        }

        if let value = exposure {
            items.append(URLQueryItem(name: "exp", value: String(value)))
        }

        if let value = gamma {
            items.append(URLQueryItem(name: "gam", value: String(value)))
        }

        if let value = highlight {
            items.append(URLQueryItem(name: "high", value: String(value)))
        }

        if let value = hue {
            items.append(URLQueryItem(name: "hue", value: String(value)))
        }

        if let value = invert {
            items.append(URLQueryItem(name: "invert", value: String(value)))
        }

        if let value = saturation {
            items.append(URLQueryItem(name: "sat", value: String(value)))
        }

        if let value = shadow {
            items.append(URLQueryItem(name: "shad", value: String(value)))
        }

        if let value = sharpen {
            items.append(URLQueryItem(name: "sharp", value: String(value)))
        }

        if let value = vibrance {
            items.append(URLQueryItem(name: "vib", value: String(value)))
        }

        // Size Properties

        if let value = width {
            items.append(URLQueryItem(name: "w", value: String(describing: value)))
        }

        if let value = height {
            items.append(URLQueryItem(name: "h", value: String(describing: value)))
        }

        if let value = fit {
            items.append(URLQueryItem(name: "fit", value: value.rawValue))
        }

        if let value = scale {
            items.append(URLQueryItem(name: "dpr", value: String(describing: value)))
        }

        if let value = crop {
            items.append(URLQueryItem(name: "crop", value: value.map({ $0.rawValue }).joined(separator: ",")))
        }

        if let value = cropRect {
            let serialized = "\(value.origin.x),\(value.origin.y),\(value.width),\(value.height)"
            items.append(URLQueryItem(name: "rect", value: serialized))
        }

        // Format Properties

        if let value = format {
            items.append(URLQueryItem(name: "fm", value: value.rawValue))
        }

        if let value = DPI {
            items.append(URLQueryItem(name: "dpi", value: String(value)))
        }

        if let value = lossless {
            items.append(URLQueryItem(name: "lossless", value: String(value)))
        }

        if let value = quality {
            items.append(URLQueryItem(name: "q", value: String(value)))
        }

        if let value = colorQuantization {
            items.append(URLQueryItem(name: "colorquant", value: String(value)))
        }

        // Background Properties

        if let value = backgroundColor, let hex = value.hexadecimalColorString {
            items.append(URLQueryItem(name: "bg", value: hex))
        }

        // Text properties

        if let value = textColor, let hexadecimalString = value.hexadecimalColorString {
            items.append(URLQueryItem(name: "txtclr", value: hexadecimalString))
        }

        if let value = textSize {
            items.append(URLQueryItem(name: "txtsize", value: String(describing: value)))
        }

        return items
    }
}

// MARK: - Adjustment Options

extension ImageOptions {

    /// Adjusts the brightness of the image. Values are clamped to the range
    /// `-100...100`.
    ///
    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-bri)
    public var brightness: Int? {
        get { return storage[.brightness] as? Int }
        set { storage[.brightness] = newValue?.clamped(to: -100...100) }
    }

    /// Adjusts the contrast of the image. Values are clamped to the range
    /// `-100...100`.
    ///
    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-con)
    public var contrast: Int? {
        get { return storage[.contrast] as? Int }
        set { storage[.contrast] = newValue?.clamped(to: -100...100) }
    }

    /// Adjusts the exposure of the image. Values are clamped to the range
    /// `-100...100`.
    ///
    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-exp)
    public var exposure: Int? {
        get { return storage[.exposure] as? Int }
        set { storage[.exposure] = newValue?.clamped(to: -100...100) }
    }

    ///Adjusts gamma/midtone brightness. Values are clamped to the range
    /// `-100...100`.
    ///
    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-gam)
    public var gamma: Int? {
        get { return storage[.gamma] as? Int }
        set { storage[.gamma] = newValue?.clamped(to: -100...100) }
    }

    /// Adjusts the highlight tonal mapping of an image while preserving spatial
    /// detail. Values are clamped to the range `-100...100`.
    ///
    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-high)
    public var highlight: Int? {
        get { return storage[.highlight] as? Int }
        set { storage[.highlight] = newValue?.clamped(to: -100...100) }
    }

    /// Changes the overall hue, or tint, of the source pixels. Values are
    /// clamped to the range `0...359`.
    ///
    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-hue)
    public var hue: Int? {
        get { return storage[.hue] as? Int }
        set { storage[.hue] = newValue?.clamped(to: 0...359) }
    }

    /// Inverts all the pixel colors and brightness values within the image
    /// producing a negative of the image.
    ///
    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-invert)
    public var invert: Bool? {
        get { return storage[.invert] as? Bool }
        set { storage[.invert] = newValue }
    }

    /// Adjusts the saturation of the image. Values are clamped to the range
    /// `-100...100`.
    ///
    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-sat)
    public var saturation: Int? {
        get { return storage[.saturation] as? Int }
        set { storage[.saturation] = newValue?.clamped(to: -100...100) }
    }

    /// Adjusts the shadow tonal mapping of an image while preserving spatial
    /// detail. Values are clamped to the range `-100...100`.
    ///
    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-shad)
    public var shadow: Int? {
        get { return storage[.shadow] as? Int }
        set { storage[.shadow] = newValue?.clamped(to: -100...100) }
    }

    /// Sharpens the image details using luminance sharpening. Values are
    /// clamped to the range `0...100`.
    ///
    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-sharp)
    public var sharpen: Int? {
        get { return storage[.sharpen] as? Int }
        set { storage[.sharpen] = newValue?.clamped(to: 0...100) }
    }

    /// Adjusts the vibrance of an image while keeping pleasing skin tones.
    /// Values are clamped to the range `-100...100`.
    ///
    /// - seealso: [Imgix Reference](https://www.imgix.com/docs/reference/adjustment#param-vib)
    public var vibrance: Int? {
        get { return storage[.vibrance] as? Int }
        set { storage[.vibrance] = newValue?.clamped(to: -100...100) }
    }
}

// MARK: - Size Options

extension ImageOptions {
    public enum FitMode: String {
        case crop = "crop"
        case clip = "clip"

// MARK: - Format Options

extension ImageOptions {

    /// The output format to convert the image to.
    ///
