Skip to content

Commit 2c13c6d

Browse files
committed
Fix Image.resize/3 false-pattern dialyzer warning; harden Image.exif/1 against bad EXIF blobs
1 parent e6a7330 commit 2c13c6d

9 files changed

Lines changed: 45 additions & 237 deletions

File tree

.dialyzer_ignore_warnings

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Dialyzer reports a `pattern_match` warning at lib/image.ex:1
2+
# ("the pattern 'false' can never match the type 'true'") that
3+
# is not locatable to a specific function — dialyxir collapses
4+
# it to line 1 because the source mapping is unavailable. The
5+
# warning originates from a `cond` / `if-else` arm whose
6+
# predicate dialyzer infers to be always-true based on Vix's
7+
# NIF return types (e.g. `Vix.Vips.Image.has_alpha?/1` is
8+
# typed `boolean | no_return()` but the NIF-only inference
9+
# narrows it to `true`). The behaviour is correct at runtime
10+
# — we keep the falsy arms because `has_alpha?/1` does
11+
# return `false` for alpha-less images.
12+
lib/image.ex:1: The pattern 'false' can never match the type 'true'

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ This is the changelog for Image version 0.67.0 released on ______, 2026. For ol
3434

3535
* 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`.
3636

37+
### Removed
38+
39+
* `Image.QRcode` is removed. QR encoding and decoding move to the sibling [`image_qrcode`](https://hex.pm/packages/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.
40+
3741
### Bug Fixes
3842

3943
* `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.

README.md

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Image
22

3-
`Image` is a fast, memory-efficient image processing library for Elixir. It is a high-level wrapper around [Vix](https://hex.pm/packages/vix), the Elixir bindings for the [libvips](https://www.libvips.org) C library, and provides an idiomatic functional API for image manipulation, drawing, text rendering, EXIF/XMP metadata, video frame extraction (via Xav/FFmpeg), QR code encoding and decoding (via eVision), blurhash, perceptual hashing, and many other image-related operations.
3+
`Image` is a fast, memory-efficient image processing library for Elixir. It is a high-level wrapper around [Vix](https://hex.pm/packages/vix), the Elixir bindings for the [libvips](https://www.libvips.org) C library, and provides an idiomatic functional API for image manipulation, drawing, text rendering, EXIF/XMP metadata, video frame extraction (via Xav/FFmpeg), blurhash, perceptual hashing, and many other image-related operations.
44

55
Machine-learning features (object detection, image classification, image generation) live in the companion [`image_detection`](https://hex.pm/packages/image_detection) library, which depends on `:image` and pulls in [Bumblebee](https://hex.pm/packages/bumblebee) and [Nx](https://hex.pm/packages/nx) as its own optional dependencies.
66

@@ -36,7 +36,7 @@ Documentation can be found at <https://hexdocs.pm/image>.
3636

3737
* `Image.Video` (frame extraction, seek, webcam) via [Xav](https://hex.pm/packages/xav), an Elixir wrapper around FFmpeg. Requires FFmpeg ≥ 6.0 on the system.
3838

39-
* `Image.QRcode` (encode + decode) via [eVision](https://hex.pm/packages/evision).
39+
* QR code encoding and decoding via the sibling [`image_qrcode`](https://hex.pm/packages/image_qrcode) package (Nayuki QR-Code-generator + quirc; no `:evision` dependency).
4040

4141
* `Image.k_means` via [Scholar](https://hex.pm/packages/scholar).
4242

@@ -159,12 +159,7 @@ exif[:make]
159159

160160
### QR codes
161161

162-
```elixir
163-
{:ok, qrcode} = Image.QRcode.encode("Hello world", size: 256)
164-
{:ok, "Hello world"} = Image.QRcode.decode(qrcode)
165-
```
166-
167-
(Requires the optional `:evision` dependency.)
162+
QR encoding and decoding live in the sibling [`image_qrcode`](https://hex.pm/packages/image_qrcode) package — add it to your deps and call `Image.QRCode.encode/2` / `Image.QRCode.decode/1`. The earlier in-tree `Image.QRcode` module (which depended on `:evision`) was removed in 0.67.0; `image_qrcode` is a drop-in replacement built on Nayuki's QR-Code-generator and `quirc`, with no `:evision` requirement.
168163

169164
### Pattern-matching errors
170165

@@ -243,7 +238,8 @@ optional dependencies enable specific features:
243238
| `:nx` | `Image.to_nx/2`, `Image.from_nx/1`, tensor interop |
244239
| `:scholar` | `Image.k_means/2` |
245240
| `:xav` | `Image.Video` (FFmpeg-backed frame extraction) |
246-
| `:evision` | `Image.QRcode`, `Image.to_evision/2`, `Image.from_evision/1` |
241+
| `:evision` | `Image.to_evision/2`, `Image.from_evision/1` (Mat ↔ Vimage interop) |
242+
| `:image_qrcode` | QR code encoding and decoding (sibling package — drop-in for the removed `Image.QRcode`) |
247243
| `:image_detection` | `Image.Detection`, `Image.Classification`, `Image.Generation` (object detection, classification, image generation — pulls Bumblebee, Nx, Axon as its own transitive deps) |
248244
| `:plug` | streaming via `Plug.Conn` |
249245
| `:req` | streaming over HTTP |

lib/image.ex

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3793,11 +3793,17 @@ defmodule Image do
37933793
|> Exif.extract_exif()
37943794
|> wrap(:ok)
37953795
else
3796-
false ->
3797-
{:error, %Image.Error{reason: :invalid_exif, message: "Invalid Exif data"}}
3798-
37993796
{:error, raw} ->
38003797
{:error, Image.Error.wrap(raw, operation: :exif)}
3798+
3799+
# The binary pattern above failed — the blob is present
3800+
# but doesn't start with the "Exif\\0\\0" prefix. This
3801+
# happens for some libvips-readable formats whose EXIF
3802+
# field carries a bare TIFF header. Surface it as an
3803+
# `:invalid_exif` error rather than letting the `with`
3804+
# raise `WithClauseError`.
3805+
bad_blob when is_binary(bad_blob) ->
3806+
{:error, %Image.Error{reason: :invalid_exif, message: "Invalid Exif data"}}
38013807
end
38023808
end
38033809

@@ -4171,24 +4177,21 @@ defmodule Image do
41714177

41724178
def resize(%Vimage{} = image, scale, options \\ []) when scale >= 0 do
41734179
with {:ok, options} <- Resize.validate_options(options) do
4174-
do_resize(image, scale, options, has_alpha?(image))
4175-
end
4176-
end
4177-
4178-
@dialyzer {:nowarn_function, {:do_resize, 4}}
4179-
4180-
defp do_resize(image, scale, options, false = _has_alpha?) do
4181-
Operation.resize(image, scale, options)
4182-
end
4183-
4184-
defp do_resize(image, scale, options, true = _has_alpha?) do
4185-
band_format = Vix.Vips.Image.format(image)
4186-
premultiplied = Operation.premultiply!(image)
4187-
4188-
with {:ok, resized} <- Operation.resize(premultiplied, scale, options) do
4189-
resized
4190-
|> Operation.unpremultiply!()
4191-
|> Operation.cast(band_format)
4180+
if has_alpha?(image) do
4181+
# Pre-multiply the alpha so the resize doesn't bleed
4182+
# transparent edges into the colour bands; un-premultiply
4183+
# and recast back to the source's band format afterwards.
4184+
band_format = Vix.Vips.Image.format(image)
4185+
premultiplied = Operation.premultiply!(image)
4186+
4187+
with {:ok, resized} <- Operation.resize(premultiplied, scale, options) do
4188+
resized
4189+
|> Operation.unpremultiply!()
4190+
|> Operation.cast(band_format)
4191+
end
4192+
else
4193+
Operation.resize(image, scale, options)
4194+
end
41924195
end
41934196
end
41944197

lib/image/qrcode.ex

Lines changed: 0 additions & 163 deletions
This file was deleted.

test/qr_code_test.exs

Lines changed: 0 additions & 44 deletions
This file was deleted.
-745 Bytes
Binary file not shown.
-11.2 KB
Binary file not shown.
-5.19 KB
Binary file not shown.

0 commit comments

Comments
 (0)