Skip to content

Releases: elixir-image/image

Image version 0.71.0

Choose a tag to compare

@kipcole9 kipcole9 released this 08 Jul 01:44

Added

  • Adds ### Examples doctest sections to more than 220 public functions across Image and its submodules. All examples run as doctests.

  • Adds a range argument (:limited or :full) to the Image.YUV encode and decode functions, and Image.YUV.valid_ranges/0. :limited (studio-swing) is the default and matches the previous behaviour.

Fixed

  • Fix Image.YUV to use studio-swing (limited range) coefficients in both directions by default. Previously the encoder was studio-swing but the decoder was full-range, so YUV round trips compressed colors toward grey.

Removed

  • Removes the unused internal modules Image.Nx, Image.Exif.ReadError and Image.Exif.FieldNames. These were private implementation modules with no callers.

Image version 0.70.0

Choose a tag to compare

@kipcole9 kipcole9 released this 07 Jul 20:51

Added

  • Adds Image.Lqip.Css to encode an image as a packed LQIP CSS placeholder value, with a new guide including the copy-and-paste stylesheet. Thanks to @hlindset for the PR.

  • Adds Image.BackgroundColor to resolve background color specifications (including :average) into concrete pixels; used by Image.rotate/3, affine and warp operations. Thanks to @hlindset for the PRs.

  • Adds :background color resolution to Image.write/3 so alpha images flatten onto a resolved background when writing to formats without alpha. Thanks to @hlindset for the PR.

  • Adds Image.YUV.valid_encodings/0 and Image.YUV.valid_colorspaces/0.

Changed

  • Image.average/1 now weights the average by the alpha band so transparent pixels do not contribute; fully transparent images fall back to the unweighted color-band average, and float-format images return unrounded averages.

  • All validation and libvips errors are now returned as {:error, %Image.Error{}} as t:Image.error/0 documents; previously many paths returned bare strings.

  • Image.to_nx/2 with shape: :whc now performs a true transpose; Image.from_nx/1 interprets unnamed tensors as {height, width, bands} and narrows 64-bit integer tensors to 32 bits.

  • Image.circle/2 center-crops non-square images and merges an existing alpha band instead of adding a second one.

  • Image.embed/4 honors :mirror/:repeat/:copy extend modes and explicit background colors on images with an alpha band.

  • Image.compose/3 with a composition list applies top-level options as per-entry defaults instead of ignoring them.

  • Image.join/2 accepts the documented :top and :center alignment values.

Fixed

  • Fix the alpha scale for :lab, :lch and :labs interpretations in Image.Pixel.to_pixel/3 — opaque alpha is 255, matching libvips. Thanks to @hlindset for the PR.

  • Fix Image.Math.divide/2 for two numbers (it previously subtracted) and Image.dominant_color/2 black-bin correction for alpha images.

  • Fix Image.Draw functions on greyscale and CMYK images, Image.Shape.line/5 end coordinate, and Image.Shape.star/2 accepting 3 points as documented.

  • Fix crashes on malformed EXIF payloads, non-base83 blurhash input, GIF writes with :effort, extensionless write paths, and Image.stream!/2 with small :buffer_size values.

  • Fix Image.YUV round trips for odd image dimensions and Image.trim/2 with an explicit :background on alpha images.

  • Fix Image.thumbnail!/3 and Image.straighten_perspective/3 error contracts, Image.from_req_stream/2 timeout handling, and Image.equalize/2 on constant-color bands.

  • Fix Image.write/3 to a t:File.Stream.t/0 to infer the image type from the stream path's file extension when no :suffix option is given.

Image version 0.69.0

Choose a tag to compare

@kipcole9 kipcole9 released this 18 Jun 08:24

Enhancements

  • Update Image.rotate/3 to use Vix.Vips.Operation.rot/2 as a fast path for rotations that are multiples of 90 when no displacement options are set. Falls back to Vix.Vips.Operation.rotate/3 otherwise. Thanks to @hlindset for the PR. Closes #199.

Image version 0.68.0

Choose a tag to compare

@kipcole9 kipcole9 released this 31 May 01:39

Bug Fixes

  • Fix remove_metadata/2 with :xmp selector. It was a silent no-op due to a typo in the header map. Thanks to @hlindset for the PR. Closes #198.

Image version 0.67.0

Choose a tag to compare

@kipcole9 kipcole9 released this 03 May 01:10

Enhancements

  • Adds Image.vignette/2.

  • Adds Image.Palette.extract/2 — perceptual palette extraction from an image via K-means in Oklab plus the Color.Palette.Cluster merge / phantom-guard / centroid-aware-rep pipeline (requires :scholar and :nx).

  • Adds Image.gamma/2 — per-band gamma curve adjustment (wraps Vix.Vips.Operation.gamma/2).

  • Adds Image.sepia/2 — single-pass sepia tone via a 3×3 colour-recombination matrix; accepts a 0.0..1.0 strength that blends the matrix with the identity (matching imgix's sepia=N percentage).

  • Adds Image.posterize/2 — quantises each band to 2..256 evenly-spaced levels for a flat-shaded, comic-strip rendering.

  • Adds Image.opacity/2 — multiplies the alpha band by a 0.0..1.0 factor; adds an opaque alpha band first when the input has none.

  • Adds Image.set_orientation/2 — overrides the EXIF orientation tag without rotating the underlying pixels (paired with Image.open/2's default autorotate: false for full caller control over orientation; matches imgix's or=N).

  • Adds :lossy and :chroma_subsampling options to Image.write/3. :lossy (boolean) toggles the lossless wire format on WebP / AVIF and palette-quantisation on PNG. :chroma_subsampling selects :auto / :on (4:2:0) / :off (4:4:4) on JPEG and AVIF.

  • Adds Image.tint/2 — colour-tinted monochrome via a single 3×3 luminance + tint colour-recombination matrix. Used for the imgix monochrome=#hex / ImageKit e-monochrome family of CDN options.

  • Adds Image.fade/2 — alpha-gradient fade-out on one or more edges via SVG <linearGradient> masks combined with a per-pixel min. Supports :top, :bottom, :left, :right, or :all, with :length as either pixels or a fraction of the relevant dimension. Used for Cloudinary e_fade.

  • Adds Image.drop_shadow/2 — soft drop shadow under the image's alpha-shaped silhouette. Composites a Gaussian-blurred, opacity-scaled, tinted copy of the alpha band beneath the original. Used for ImageKit e-shadow and Cloudinary e_shadow.

  • Adds Image.minimize_metadata/2 with a :keep option — caller-controlled list of EXIF fields to preserve when minimising metadata. The 1-arity variant continues to default to [:copyright, :artist]; the 2-arity variant lets callers pass keep: [:copyright] (preserve only copyright), keep: [] (strip everything), or any other subset.

  • Adds Image.enhance/2 — content-aware automatic enhancement composed of luminance equalisation + mild saturation boost + mild sharpen. Approximates the CDN-style "improve" / "auto-enhance" calls used by Cloudinary, imgix, and ImageKit. Tunable via :saturation and :sharpen_sigma options.

  • Adds Image.to_colorspace/3 — ICC-profile-driven colourspace conversion. Accepts the libvips built-in profile atoms (:srgb, :cmyk, :p3) or a path to an .icc file via Image.ICCProfile.known?/1 validation. Options: :input_profile, :intent (:relative / :perceptual / :saturation / :absolute), and :depth (8 / 16). Wraps Vix.Vips.Operation.icc_transform/3.

Removed

  • Image.QRcode is removed. QR encoding and decoding move to the sibling image_qrcode package, which is built on Nayuki's QR-Code-generator + quirc and does not depend on :evision. Migration: replace Image.QRcode.encode/2 / Image.QRcode.decode/1 with Image.QRCode.encode/2 / Image.QRCode.decode/1 (note the capital "C") and add {:image_qrcode, "~> 0.1"} to your deps. The Image.to_evision/2 and Image.from_evision/1 interop helpers are unchanged.

Bug Fixes

  • Image.add_alpha/2's :opaque and :transparent atoms now produce alpha = 255 and alpha = 0 respectively, matching the standard libvips / RGBA convention. The previous values were inverted relative to their names; integer values pass through unchanged.

Image version 0.66.0

Choose a tag to compare

@kipcole9 kipcole9 released this 25 Apr 12:53

Enhancements

  • Relax Nx support to allow ~> 0.10 (not ~> 0.11). This allows Bumblebee to be configured in image_vision since Bumblebee only supports Nx ~> 0.9.0 or ~> 0.10.0.

Image version 0.65.0

Choose a tag to compare

@kipcole9 kipcole9 released this 09 Apr 14:34

Bug Fixes

  • Use the updated colorspace names in Color 0.4.0.

Image version 0.64.0

Choose a tag to compare

@kipcole9 kipcole9 released this 09 Apr 06:03

The primary intent of this release is to stablise the code in readiness for a 1.0 release. Deprecated code has been removed, a standardised color model introduced (via the new library color) and objective classification and detection has been removed to a new image_detection library.

Breaking Changes

  • Five long-deprecated functions have been removed:

    • Image.interpretation/1 — use Image.colorspace/1.
    • Image.type/1 (was format/1) — use Image.band_format/1.
    • Image.convert_to_mask/1 — use Image.convert_alpha_to_mask/1.
    • Image.convert_to_mask!/1 — use Image.convert_alpha_to_mask!/1.
    • Image.map_pages/2 — use Image.map_join_pages/2.
  • Image.Color has been removed. Color handling now lives in two new modules and one new dependency:

  • Image.Classification and Image.Generation have moved to a new sibling package, :image_detection.

  • :bumblebee is no longer a dependency of :image. It is configured in the new library image_detection.

  • Image.Video is now backed by Xav (a thin Elixir wrapper around FFmpeg) instead of :evision / OpenCV. The public API surface is largely unchanged but the underlying type, options, and a few semantic details have moved:

    • The video struct is now %Image.Video{} (with fields :reader, :source, :fps, :duration_seconds, :frame_count, :width, :height) rather than %Evision.VideoCapture{}. Pattern-match on the new struct module if your code does so.

    • Image.Video.open/2's :backend option has been removed. FFmpeg picks the demuxer automatically and there is no concept of pluggable backends in Xav.

    • Image.Video.known_backends/0, available_backends/0, known_backend?/1, known_backend_values/0, and available_backend?/1 have been removed for the same reason. Image.Options.Video (the module that owned the backend table) has been deleted.

    • Camera input is now opened via a platform-specific device path. :default_camera resolves to /dev/video0 on Linux, "0" (AVFoundation device 0) on macOS, and "video=0" on Windows. An integer camera index is mapped to the corresponding /dev/videoN (or platform equivalent). For non-default cameras you can also pass an explicit FFmpeg device string.

    • Frame-based seeking (Image.Video.seek/2 with frame: n, and Image.Video.image_from_video/2 with frame: n) is now implemented as a time-based seek to n / fps followed by zero or more next_frame calls. For most files this lands on the requested frame; for very inter-frame-compressed files FFmpeg may snap to the nearest preceding keyframe.

    • Image.Video.close/1 is now a no-op that returns {:ok, %Image.Video{reader: nil}}. Xav garbage-collects the underlying FFmpeg context, so explicit close is no longer necessary. The function is retained for source compatibility; subsequent operations on the closed struct return {:error, %Image.Error{reason: :video_closed}}.

    • Image and audio frames are decoded by FFmpeg + libswscale rather than by OpenCV's videoio backend. Pixel-exact comparisons against fixtures generated by the previous version will not match; the test fixture test/support/validate/video/video_sample_frame_0.png has been regenerated.

  • :xav is now an optional dependency. It requires FFmpeg ≥ 6.0 to be installed on the system. Add it to your mix.exs if you use Image.Video:

    {:xav, "~> 0.10", optional: true}
    
  • :evision is no longer needed for Image.Video. It is still required for Image.QRcode and for the Image.to_evision/2 / Image.from_evision/1 interop helpers, which are unchanged. The README's optional-dependency table reflects the new split.

  • Image.Error is now a structured public exception. It carries :reason (atom or {atom, value} tuple), :operation, :path, :value, and a derived :message. Every fallible function in the library now returns {:ok, value} or {:error, %Image.Error{}} — bare-string error tuples have been eliminated. Bang variants raise the same struct. The new Image.Error.wrap/2 helper attaches structured context to a raw libvips or File.* error. Pattern-match on :reason instead of scraping :message:

    case Image.open(path) do
      {:ok, image} -> ...
      {:error, %Image.Error{reason: :enoent}} -> not_found_handler()
      {:error, %Image.Error{} = err} -> raise err
    end
    

Enhancements

  • Image.dominant_color/2 now accepts a :method option of either :histogram (the existing default) or :imagequant. The :imagequant method routes through libimagequant (via vips_gifsave_buffer) and returns a palette of {r, g, b} tuples ordered by perceptual importance. New :effort and :dither options tune the quantiser. See guides/performance.md for a comparison of the two methods.

  • Colour arguments to drawing, embedding, trimming, gradient, chroma key, comparison, warp-perspective, meme, replace-colour, flatten, and if_then_else operations are now correctly converted to the target image's colour space. Drawing :red on a Lab image now produces actual Lab red, not the bytes [255, 0, 0] reinterpreted as Lab.

  • New Image.Pixel and Image.ICCProfile modules. Image.Pixel.to_pixel/3 is the canonical way to turn any user-friendly colour input into a libvips-ready pixel for a particular image; Image.Pixel.to_srgb/1 is the image-independent equivalent for callers (SVG renderers, gradients) that need a fixed sRGB output.

  • The :color library is now a dependency of :image. It is now the canonical colour science layer for the project.

  • Image.Video now supports HTTP/HTTPS/RTSP/RTMP URLs as video sources for free, since FFmpeg supports them natively.

  • Image.xav_configured?/0 is the new compile-time predicate that gates the Image.Video module (analogous to Image.evision_configured?/0 and Image.bumblebee_configured?/0).

  • The Image.Video.frame_to_image/1 helper exposes the raw Xav.FrameVix.Vips.Image.t() conversion (used internally by image_from_video/2 and stream!/2). Useful if you have a frame from elsewhere in the Xav ecosystem and want to bring it into Image.

Image version 0.63.0

Choose a tag to compare

@kipcole9 kipcole9 released this 05 Feb 23:32

Breaking Changes

  • Image is supported on Elixir 1.16 and greater only. Previously support was claimed for Elixir 1.12 and later.

Bug Fixes

  • Fix compiler warnings on Elixir 1.20.

  • Fix RGB to hex conversion. Thanks to @akoutmos for the issue and PR. Closes #196.

Image version 0.62.1

Choose a tag to compare

@kipcole9 kipcole9 released this 22 Oct 21:59

Bug Fixes

  • Fix decoding EXIF component configuration if the value is invalid. Fixes #194. Thanks to @ethangunderson for the report.

  • Fix Image.minimize_metadata/1 when the image does not have an author or copyright field.