Repository navigation
v0.4.0
image_pipe_url
Added
- With
:validate_against,ImagePipe.URL.validate/1warns about acrop
orregionthat covers the whole frame, as an:inert_optionissue with
detail: :full_frame. It also warns about the options only that crop or
region made useful, such asanchor.
Changed
- Parsing a URL takes less time, most of all for a URL without presets or
unsetwhen no request defaults are set. - A
watermark_source:loses an optional leading/, as the main source
does, so itswm-src64value and its deterministicwm-enctoken change. - A URL's
anchor-offset,extend-at, orextend-offsetno longer
replaces the crop target or canvas from a preset or the request defaults,
and itsunsetremoves only that option. Before,anchor-offset=5,5
dropped a preset'sanchor, andextend-at=unsetdropped itsextend. - A
trimtolerance above2147483647is rejected, both in a URL and by
ImagePipe.URL.group/2. - Errors from parsing a URL state the accepted range for sizes, offsets,
pad,wm-gap,dpr,zoom,crop-ratio,detect,blur,
progressive-blur,sharpen,pixelate,contrast,saturation, and
thetrimtolerance, and show whichgradientvalues are optional. - An invalid value given to the URL builder appears in its error, with the
accepted range for numbers, as ininvalid value for :dpr option: expected a number above 0, at most 2147483647, got: 0. An invalid source
isn't repeated in its error. - Breaking: The
:presetsin:validate_againstmust be a map with
string keys. Change a list of{name, fragment}pairs to a map such as
%{"card" => "w=400"}.ImagePipe.URL.config/1raises an
ArgumentErrorthat names:presetsfor any other value, instead of a
Protocol.UndefinedError. - A
regionwhose x or y is0pctgives the same plan as one written with
0. detect=all:3, one weight for every class, gives the same plan as
detect=all.crop=100pct,100pctwithoutcrop-ratio, andregion=0,0,100pct,100pct,
give the same plan as the request without them.autoqualityandmax-bytesare reported as ignored options with
format=webpand losslesswebp-options, as withformat=png.- Checking and decoding the percent escapes of a
srcsource takes less
time, up to about 0.5 ms less for an 8 KB source.
Removed
- Breaking:
request_watermarksinvalidate_against, and the
:watermark_source_disabledissue. Servers accept every watermark source
that a configured source serves. Remove the option fromvalidate_against. - Breaking:
mount:onImagePipe.URL.Helpers. Start an instance with
the mount's URL settings and pass its name asinstance:.
Fixed
- The URL builder's validation against a server's presets resolves them
the way the server does: a value that a later layer unsets or replaces no
longer removes what it would have replaced, and a preset that names other
presets gives the same result as listing them. ImagePipe.URL.url/3returns{:error, :invalid_source}for a source
that is just/or holds a NUL byte, which the server refuses.
ImagePipe.URL.group/2rejects such awatermark_source:.- Checking a URL with
:validate_againstapplies the request defaults to
the URL's first group, as the server does, even when that group holds
onlyunsetoptions or names a preset the check doesn't know. Before,
the defaults were checked with the next group, so the check could
disagree with the server. - A preset in
:validate_againstwith a group holding onlyunset
options is a pipeline preset, as on the server, so adding other options
to it is an error, and a first group like that clears the request
defaults. Before, the check dropped that group. - An issue that
validate/1reports with:validate_againstnames the
group as the builder numbers it. Before, when a group held onlyunset
options, issues in later groups named the group before theirs. - With
preset_lookup: true,ImagePipe.URL.validate/1reports a
conflict between two options the plan sets, such asquality:with
autoquality: true, even when the plan names a preset missing from
:presets, andImagePipe.URL.url/3returns
{:error, {:invalid_request, issues}}. Before,ImagePipe.URL.url/3
built a URL the server rejects with400.
image_pipe
Added
- A
[:source, :svg_parse]span wraps parsing an SVG watermark, and names
the limit that rejected it, such as:svg_bytes. The default Logger logs
it withstages: true. - The
[:parse]and[:request]stops and the request summary carry
:source_url, the request's source as ImagePipe parsed it, decoded from
src64or decrypted fromenc, and:watermark_source_urls, each
group's watermark source. Each query value, such as an
X-Amz-Signature, and a URL's user name and password are replaced with
REDACTED. The default Logger writes them assource_url=and
watermark_source_url=fields, and the tracer records them on the
image_pipe.parseandimage_pipe.requestspans. See
[:parse]. ImagePipe.cache_stats/1reports the cache's stored bytes and entries,
its size cap, its hits, misses, and failed reads and writes since start,
and the entries it keeps in memory, orrefused: truewhen another node
owns its root. It also reports how many originals the instance
remembers. A cache that doesn't report its size within a second has
available: false. See
Usage and hit rate.max_remembered_originalssets how many originals an instance remembers
in memory: a local file's hash, or an origin image's cache headers. It
defaults to one for each 50 KB of:cache's:max_size_bytes, at least
10,000 and at most 200,000, and forgets originals that weren't used
recently first. A remembered file isn't read and hashed again while it's
unchanged, and the processed images of a remembered origin image are
served without downloading it again. Each takes about 1 KB for a local
file and about 1.4 KB for an image from an origin.- The
X-ImagePipe-Source-Progressivedebug header says whether a JPEG
original is progressive. See
Debug response headers. ImagePipe.Source.HTTPandImagePipe.Source.S3takepool_size, the
maximum number of open connections to each origin address or S3 endpoint.
It defaults to50, the limit that applied before. Over HTTP/1.1, raise it
when more downloads from one origin run at once, so they don't wait for a
free connection and fail with504afterpool_timeout.max_search_pixelslimits the pixels thatautoqualityandmax-bytes
encode in one request, 100,000,000 by default. A search stops early when
the next quality would pass it. On a 40-megapixel AVIF or WebP result it
tries two qualities, plus up to two encodes to deliver one, where a search
with both options could encode up to 11 times and take up to 45 seconds.
:infinityturns the limit off. The[:encode, :search]stop event
carries:pixel_limited, and its:limiting_factorcan be
:search_pixels. See
Limiting quality search work.
Changed
-
A cache whose
rootcan't be created or written logs an error at
startup that names the root and the reason, such aspermission denied,
and says images are served without being stored. Before, it logged a
warning that it "could not claim" the root. -
Breaking: The
:errormetadata of[:debug, :collect, :error]is
the module of the raised exception, such asRuntimeError, instead of
always:error. Handlers that match onerror: :errormust match the
module. -
Breaking: The HTTP source refuses origins in
192.0.0.0/24,
192.88.99.0/24, and2001::/23, which are reserved for protocol use
(the last includes Teredo tunnel addresses). They were treated as
public. To reach one, set:allow_reservedor list its range in
:allowin the source's:address_policy. -
The HTTP source allows an IPv4 origin reached through a NAT64 gateway
from an IPv6-only network, at an address such as64:ff9b::93.184.216.34,
when the IPv4 address is public. Before, every such address was refused.
An IPv4 range in:allowalso matches the NAT64 form of its addresses. -
Breaking:
ImagePipe.Source.Pathhas only asegmentsfield. Its
schemefield was alwaysnilin a path an adapter receives, so remove
scheme:from any pattern that matches the struct. -
Breaking:
storage_inputsis nowvary_on, and the
ImagePipe.run/4optionrequest_inputsis nowvary_values. Rename
them in your configuration and calls. -
Breaking: An
ImagePipe.Plugmount without:keysraises
ArgumentErroron every request unless its configuration sets
allow_unsigned: true. Serve public traffic with signed URLs, and set
allow_unsigned: trueonly for a mount that nothing outside a private
network can reach. An instance withallow_unsigned: trueand no:keys
logs a warning when it starts. Running a plan withImagePipe.run/4or
ImagePipe.write/5needs neither. See Signing URLs. -
Breaking: The
[:request]event's:path, the request summary's
path, and theurl.pathtrace attribute end at the source: the first
src,src64, orencsegment is followed byREDACTED, and the rest
of the path and the query string are left out, as in
/w=200/src/REDACTED. Eachwm-src64=value isREDACTEDtoo,
including the rest of a standard-base64 value split at its/. A path
without a source keeps its query string, andurl.query, with every
value replaced withREDACTED. Read the source from the new
:source_urlinstead. Before, the path was as sent, with only the values
of a fixed list of credential parameters redacted, so values such as a
tokenorapi_keywere logged. -
Breaking:
encandwm-enc=sources are now visible to telemetry
handlers, logs, and traces, decrypted, in:source_urland
:watermark_source_urls. Source encryption hides a source from whoever
requests the image, not from whoever reads your telemetry. If you send
telemetry somewhere you don't want your source URLs, drop these keys in
your handler. -
A request with
crop=100pct,100pctand nocrop-ratio, or with
region=0,0,100pct,100pct, is processed as the same request without them,
since they keep the whole image, and gets its cache key and ETag. The
[:request, :ignored_options]event, and withdebugthe
X-ImagePipe-Ignored-Optionsheader, list the crop or region and the
options only it made useful, such asanchor. -
Requests that use
autoqualityormax-byteshave new ETags, so
clients and CDNs download them again once after the upgrade. -
A filesystem cache misses once after upgrading for every image it stored
before, and its startup sweep deletes those entries. -
A filesystem cache's startup sweep reads its metadata files only when the
cache was last checked by an ImagePipe version with another metadata
format, so restarting a cache with many entries reads far less from disk.
The cache records the format it checked in.metadata-versionin its
root. The first start after this upgrade still reads every metadata
file. -
Breaking:
:max_blur_worklimits the total work of allblur,
progressive-blur, andsharpensteps in a request, across all its
groups, instead of each blur on its own. Each step counts the pixels of
its image times (sigma² + 10,000), so a single blur may use a slightly
smaller sigma than before (up to 624 on a 10-megapixel image). A
progressive blur counts (2 × sigma² + 50,000), so it allows a smaller
sigma (up to 418 on a 10-megapixel image), and the default limit refuses
any progressive blur on a 100-megapixel image. A request over the limit
fails with422and the message "blur and sharpen work exceeds the
limit". Raise the limit if your requests chain large blurs, or set it to
:infinityto turn it off. See
Limiting blur and sharpen work. -
When a cache with
max_size_bytesis full, it keeps images made from
progressive JPEGs longer, since they cost more to render again than
images from baseline JPEGs of the same size. Their:estimated_cost_us
on[:cache, :write]is higher. -
A
detectoranchor=smart-facecrop of a large JPEG, WebP, or SVG with
a small crop box decodes at a reduced size. Detection's minimum of 640
pixels on the long edge applies to the whole image the detector searches,
not to the crop box. A crop box that covers the whole image runs no
detection, so without afit=coverresize after it, it decodes as small
as the output allows. -
Concurrent requests for one new original share its download in more
configurations, and no longer wait for a download they would repeat. See
request coalescing.- Requests for an original from an HTTP or S3 source download it once,
whatever size or format they ask for, with or without a cache, while
up to 4,096 originals are being fetched at once. Before, without an
originals cache, requests for the same processed image waited for the
first download and then downloaded it again, and requests for other
sizes each downloaded it themselves. - Requests for an original from a source with caching turned off
download it at the same time instead of one after another. - When the original may not be stored, waiting requests start their own
downloads as soon as the origin responds, instead of after the first
download finishes.
- Requests for an original from an HTTP or S3 source download it once,
-
gradienton an 8-bit image takes less time, since it blends in one pass.
A blended color can differ from before by one level. -
autoqualityon an image with transparency takes less time. Each quality
it tries is scored against a dark and a light background at the same time
instead of one after the other. -
[:cache, :coordination]events for:sourcecome only from requests
that download an original, check a remote original with its origin, read
a local file, or wait for another request doing so. No event is emitted
for an original from a source withinternal_cache: :disabled, or for a
local file from an immutable source.:sourcecan report:bypasswith
reason: :not_storedwhen the original may not be stored. -
Breaking:
:presetsmust be a map. Change a list of
{name, fragment}pairs to a map such as%{"card" => "w=400"}. -
Breaking: An
ImagePipe.Source.S3.CredentialProvider's
fetch_credentials/1takes only the provider's options, without the
bucket, andImagePipe.Source.S3.CredentialWarmuphas no:scope
option. Credentials are now cached per provider and options and shared
by every bucket. Remove the bucket argument fromfetch_credentials,
give buckets that need different credentials different provider options
under:buckets, and removescope:from each warmup, keeping one per
provider and options. -
A WebP original scaled down a lot matches the same image as a JPEG or PNG
original. Like a JPEG, it's decoded at 1/2, 1/4, or 1/8 of its size, the
smallest that stays at or above the requested size, and the resize does the
rest. Before, it was decoded straight at the requested size, so a thumbnail
under about 200 pixels wide differed visibly from the JPEG or PNG one. -
A cache's
max_size_bytescounts each entry's body rounded up to whole
4 KiB blocks, plus one block for its metadata file, instead of the body
alone, so many tiny entries, such as BlurHash or LQIP responses, can't
take far more disk than the cap. A cache whose entries now count past its
cap evicts down to it at startup after the upgrade. -
When a cache with
max_size_bytesis full, a new processed image that
many requests want is kept sooner. The cache now counts each request that
misses it, including requests that wait for another request's copy,
instead of counting how often it's written. -
A cache with
max_size_bytesuses less than half the memory to track
its entries: about 175 bytes per stored image instead of over 400, which
saves at least 45 MB for a cache of 200,000 images. -
The default Logger shows a coordination event's reason after its result,
as inbypass (deadline). It logs a:bypasswith reason:not_stored
at its base level. -
An HTTP source whose host doesn't resolve fails with
{:source, :unresolved_host}in logs and telemetry instead of
{:source, :denied_address}. It still answers404. -
autoqualitywithmax-bytesscores only the quality it delivers, not
each quality it tries to fit the byte limit, and reuses the encodes
autoqualityalready made to narrow that search, so such a request takes
less time.[:encode, :search, :probe]spans of the:capphase no
longer carry:score. -
The hash and timestamps kept for each local file, and the cache headers
kept for each origin's image, are held in memory instead of in the
processed-image cache, for up tomax_remembered_originalsoriginals.
They no longer take its space or get evicted with its images. After a
restart, a local file is hashed again, and an image from an origin is
downloaded again, on its first request. Remembering them uses none of the
cache's 64 concurrent writes, so it never causes a processed image's
write to be dropped. -
A cache with
max_size_bytesdeletes a rejected write without syncing it
to disk. Once the popular images don't fit in the cache, that saves most
of its write I/O. A write that waits more than 5 seconds to be kept or
rejected is dropped and reported aserror: :admission_timeout.
[:cache, :admission]is now emitted before the sync, and its
:victim_countcounts the entries the write was compared with. -
[:transform, :detect, :skipped]is also emitted whenanchor=smart-face
uses attention becausedetector_requiredis set and the face model isn't
loaded yet (result: :not_ready) or the detector has no face model
(result: :unavailable). The default Logger logs both atwarning. -
When an HTTP source follows a redirect to another host, port, or scheme,
the request no longer carries the headers or credentials set in
req_options, such as an API key. Before, onlyauthorization,
proxy-authorization, andcookiewere dropped. A redirect fromhttps
tohttp, or to a URL with a user name or password, now fails with
502. -
A
blur,progressive-blur, orsharpenresult is copied to memory
before a later resize or a step that rereads pixels (rotate,trim,
pixelate,blur,progressive-blur,sharpen, or a crop with
anchor=smartordetect). Before, each of those steps recomputed the
blur for every region it read, so a request could take many times the
CPU thatmax_blur_workallows. Arotateby an angle other than a
quarter turn is copied the same way before such a step. -
A request for a cached image from a local file no longer opens the file
while its size and timestamps are unchanged. -
A cache with
max_size_byteskeeps its counts of the requests that miss
each image in 4 MB per 50 GB ofmax_size_bytes, allocated when it
starts. Before, a busy 50 GB cache's counts grew to about 70 MB and slowed
its garbage collection. -
A downloaded original is written to disk in 64 KiB batches instead of one
write per network read, which takes less CPU for a large original. -
With
format=jpeg,padandextendfill with white directly, instead
of adding transparency that the encoder then flattens onto white. This
takes less time to encode and gives the same image, unless
:max_result_width,:max_result_height, or:max_result_pixelsthen
scale it down. Then pixels next to the padding can differ. -
A
detectoranchor=smart-facerequest decodes and buffers less when no
detection will run:- The detector isn't available for any of the requested classes.
- With
detector_required: true, the detector's face model isn't ready
yet, and the request usesanchor=smart-face. - The image already has the
fit=covertarget's aspect ratio, so the crop
can't move.
When the crop falls back to attention cropping, it runs on the smaller
decode, so its result can differ slightly. -
Pixels of a source whose embedded RGB profile matches sRGB to within 1
level are used as sRGB, without converting them through the profile, for
the output, for placeholders, and forbg,gray,bitonal,
monochrome,duotone,colorize,gradient,trim, andwm. The
output still embeds the profile withprofile=preserve. With the libvips
operation cache off, this saves about 7 ms on a small JPEG resize and up
to about 220 ms on a largetrimrequest. Some pixels can differ from
before by 1 out of 255. -
The
[:request]event's:path, and the request line and the
url.pathtrace attribute that come from it, include the path that
ImagePipe.Plugis mounted at, such as/imagesfor
forward "/images", ImagePipe.Plug. Before, the mount path was left out. -
A
FileSystemcache keeps the metadata of recently served entries in
memory, so a repeated hit reads only the body file from disk. It takes
about 36 MB for a 1 GB cache, and at most about 90 MB unless
max_remembered_entriessets the number of entries. See
Cache process and restarts. -
A second instance whose cache
rootanother instance in the VM already
uses raisesArgumentErrornaming the root when it starts. Before, its
start failed with{:already_started, pid}. -
The cache, downloads, and decoding no longer queue their file operations
behind the Erlang VM's single file-server process. -
An instance keeps the bytes of up to 64 recently used watermark images in
memory, so a watermark from an origin is no longer downloaded for every
processed image. See What is stored. -
The processing pool's
max_queuedefaults to 1,024 instead of 64, so a
burst of quick requests waits for a turn instead of answering503. See
choosing values. -
With
meta=strip, a JPEG, WebP, or AVIF image at 72 dpi no longer
carries an EXIF block, so it's about 190 bytes smaller. An image at
another density, set by thedpiURL option or thestripped_dpi
setting, keeps an EXIF block with only the density. PNG still stores its
density inpHYs.
Removed
- Breaking: The originals cache: the
input_cacheoption, and the file
source'scopyoption, which kept copies of local files in it. Remove
them from your configuration, and delete the originals cache's directory.
The[:cache, :input]event and the request summary's:input_cache
field are gone, and[:cache, :sweep]no longer reports:pins. - Breaking: The
ImagePipe.Plugoptionallow_debug_headers. A
request'sdebugoption always adds the debug headers. Remove the option
from your configuration. With signed URLs, only your app can adddebug. - Breaking: The
ImagePipe.config/1optionrequest_watermarks.
Requests may always name their own watermark withwm-src64orwm-enc,
and its image still has to come from a configured source, like the main
image. Remove the option from your configuration. - Breaking:
max_preset_lookups. A request may look up any number of
presets. Remove the option from your configuration.ImagePipe.run/4
no longer returns{:preset, :too_many_presets}, and[:preset, :lookup]
no longer reports that reason. - Breaking: The instance option
mounts:, andmount:on
ImagePipe.PlugandImagePipe.url_config/2, which is now
ImagePipe.url_config/1. An instance checks URLs against its own URL
settings only. To serve URLs with different keys, such as signed and
unsigned URLs, start an instance for each, and mount it or build its URLs
by its name. Instances that use caches each need their own cache roots.
Fixed
- A
debugresponse from a configuration without a cache leaves out
X-ImagePipe-Cache-Key, as documented. Before, it carried a key that
nothing could be stored under. - When a later preset or URL option unsets or replaces a value from a
preset or the request defaults, the value that one replaced still
applies. With
request defaults offormat-q=avif:50and a presetq80defined as
q=80,/preset=q80/q=unsetencodes AVIF at 50. A preset that names
other presets gives the same result as listing them in the URL. Requests
that hit these cases get new cache keys and ETags. See
clearing inherited values. - A request that uses a preset your
:preset_lookupreturns, naming a
watermark the configuration doesn't define, or carrying awm-enctoken
that no key in:source_encryption_keysdecrypts, answers500, and
ImagePipe.run/4returns{:preset, :invalid_definition}, like other
invalid stored presets. Before, it failed with400or404, as if the
requester had made a mistake. - The daily sweep of a filesystem cache's leftover files keeps running after
one of the cache's processes restarts, and after a sweep fails. Before, it
stopped until ImagePipe restarted. - The request summary and the default Logger's request line carry
:reason
for a403invalid signature, a400for a signed URL sent to a mount
without signing keys, a410expired URL, and a404concealed source
that doesn't decrypt. Before, these lines had no reason. - A crash report from a streaming response, or from
ImagePipe.Source.S3.CredentialWarmup, no longer prints the configured
origin credentials, such as S3 keys and authorization headers. - A request path with bytes that aren't valid UTF-8 no longer makes the
telemetry path invalid UTF-8. They becomeU+FFFD. ImagePipe.config/1raises a duplicate-option error for an option given
more than once. Before, it raised an unknown-option error for it.- Requests that differ only in the header or cookie values named in
vary_onshare one download of their original and one remembered
original. Only the processed images differ. A new cachebuster still
downloads the original again. - A preset or
:request_defaultswith awm-enctoken that the
:source_encryption_keyscan't decrypt raisesArgumentErrorwhen
ImagePipe starts. Before, the configuration was accepted and every request
using the token failed with404. - A preset with
wm-enc=unsetno longer needs:source_encryption_keys. - The request defaults apply to a URL's first group even when that group
holds onlyunsetoptions. Before, they moved to the next group, so
w=unset/-/blur=2with a defaultw=200was still resized to 200
pixels wide. - A cached image whose delivery failed after its headers were sent logs its
request line aterror, with status200. Before, the line had no status
and logged atwarning. - A request that stopped waiting for another request's download of an
original, just as that download finished, no longer keeps the original
locked, even when the server takes more than 5 seconds to respond. Later
requests for that original waited behind the lock until the process that
served the request exited. - An
enlargeor akernel=nearestresize of an image with an EXIF
orientation,rotate, orflipnow gives the same pixels as rotating or
flipping first and then resizing. Before, enlarged pixels could be off by
a fraction of a pixel, and akernel=nearestdownscale picked different
source pixels. - An image delivered in sRGB without a color profile, with its metadata
kept, has the EXIFColorSpacetag set to sRGB when the original has the
tag. Before, the tag said Uncalibrated. - An image decoded while its original downloads no longer waits behind
newer requests for its encode. The encode's turn in the processing pool
goes ahead of waiting requests and keeps the deadline of its first turn.
Before, it queued at the back and could fail with a503after its
decode had finished. - An image whose EXIF date fields aren't plain numbers (such as
2024:01:01 02+02:00:00) or aren't stored as text no longer fails with
a500when metadata is stripped. All of its metadata is removed,
including the copyright and artist thatkeep_copyrightwould keep. - A watermark with an opacity below
1blends with the image at its exact
faded alpha. Before, the faded alpha was rounded to a whole level first,
so a watermark's edges or faint areas could be off by one level. - An S3 credential fetch from a provider that hangs fails after 30 seconds,
and the next request fetches again. Before, a hung fetch held up every
request for those credentials until the server restarted. - An HTTP source fetch that runs out of
fetch_timeoutwhile waiting for a
pooled connection reports:receive_timeoutin logs and telemetry
instead of:pool_timeout. When the source's host resolves to several
addresses, a fetch no longer tries the next one with less than 100 ms of
fetch_timeoutleft. - A request with
format=webpand losslesswebp-optionsno longer fails
with400when it, a preset, or the request defaults setautoqualityor
max-bytes. The options are ignored, as withformat=png. - A preset group holding only
unsetoptions is kept when the preset is
compiled, so a first group like that clears the request defaults, as the
same groups written in the URL do. Before, the defaults applied to the
preset's next group:blur=unset/-/w=100with a defaultblur=3still
blurred. A preset with such a group, in any position, is a pipeline
preset, so adding other options to it answers400. - A
:presetsoption that isn't a map with string keys, such as a string
ornil, raises anArgumentErrorthat names:presetsinstead of a
Protocol.UndefinedError. - A
regionwhose x or y is0pctgets the cache key and ETag of the same
region written with0. - A detector whose
warmup/1raises no longer stops itsImagePipe
instance. The warmup logs a warning and retries, as it does for
{:error, reason}. A detector whoseavailable?/1raises is skipped
with a warning. Before, these crashes used up the instance's restart
limit, and every later request raised. A crash report from the warmup no
longer prints the detector's options. detect=all:3, one weight for every class, gets the cache key and ETag of
detect=all, since a weight that every class shares doesn't move the
crop.- The request summary of a
HEADrequest that misses the cache no longer
reports body bytes (:bytes, and the size in its log line), since none are
sent. - A request whose
srcURL holds a user name or password that isn't valid
UTF-8 once decoded keeps its request summary log line. - A
cropplaced by an anchor, including the default center, or by a focus
point, followed by a resize, now lands where it would on a full-size
decode. Before, a source decoded at a reduced size for the resize could
place it up to one output pixel off. - Downscaling a 16-bit image or watermark without transparency, including
the scale-down to fit the result limits or a format's size limit, no
longer darkens it slightly. Each value lost about 0.1%, so white came
back as 65455 instead of 65535. - An image scaled down to fit
:max_result_width,:max_result_height,
:max_result_pixels, or a format's size limit keeps the exact colors of
its semi-transparent pixels and uses the request'skernel. Before, a
semi-transparent pixel could lose a level, so[200, 120, 37, 100]became
[200, 119, 37, 100], and akernel=nearestresult was smoothed. - When the first load of the face or object model failed, each detection
request waiting for it tried the load again in turn, so the last one
waited for every attempt. Those requests now fail with the first load's
error at once: the crop falls back to attention, or the request fails with
500whendetector_requiredis set. The next request loads again. png-options=palette,bitdepth:16multiplies 8-bit color by 257, as
bitdepth:16withoutpalettedoes. Before, it multiplied by 256.png-options=bitdepth:1,2, or4on a 16-bit color or gray-and-alpha
result encodes at 8 bits, as on an 8-bit one. Before, it encoded at
16 bits.- A
crop-ratiofar from thecropsize's own ratio, such as1:4000
for a square crop, no longer fails with a500. Each side of the crop
keeps at least one pixel. - A client error, such as a
404for a missing source, on a URL with
expirescaps a host'sCache-Controlat the URL's expiry, as an image
response does. Before, a host'smax-age=86400kept the error cached
after the URL expired. Errors found while parsing the URL, and the410
for an expired URL, keep the host'sCache-Controlunchanged. - A thin
croporregionof a JPEG or WebP image blends in far fewer
pixels from just outside it. A crop under 16 pixels on its short side,
such as an 8-pixel strip, decodes at full size and takes in none.
Otherwise the reduced-size decode shrinks by at most an eighth of the
crop's short side. - When none of an HTTP source's addresses accepts a connection or answers
in time, the source keeps its cached DNS answer while it looks the host
up again, and replaces the answer only when it changed. Before, the
cached answer was dropped first, so other requests to that origin waited
for a new lookup. output=inforeports an exactdprfor a tall, narrow image that the
result limits scale down. Before, a 7×4000 image scaled to 2×1000
reporteddpr0.29 instead of 0.25. The:scalemeasurement of
[:output, :clamp]now uses the long side too.- A URL's
anchor-offset,extend-at, orextend-offsetno longer
replaces the crop target or canvas from a preset or the request defaults,
and itsunsetremoves only that option. Before,anchor-offset=5,5
dropped a preset'sanchor. See
presets. - A
trimtolerance with hundreds of digits no longer fails with a500.
Tolerances above2147483647now fail with a400. - Resizing a JPEG or WebP image only a few pixels high or wide, such as a
2000×3 image atw=100, no longer fails with a415. Neither does
shrinking a WebP so far that its short side would be under one pixel, such
asw=1/dpr=0.5. - S3 credentials that a provider returns with less than five minutes left
are fetched again at half their remaining time, instead of every five
seconds until the provider rotates them. - A cache's own events,
[:cache, :admission],[:cache, :eviction, :stop],
and[:cache, :sweep], use the instance'stelemetry_prefix. Before, they
always used[:image_pipe]. Handlers attached for a custom prefix,
includingattach_default_logger(prefix: ...)andattach_tracer, now get
them, and handlers attached under[:image_pipe]for such an instance no
longer do. - An issue that
ImagePipe.validate/2orImagePipe.run/4reports, and
the:locationsof[:request, :ignored_options], name the group as it's
written in the URL or in the URL builder. Before, when a group held only
unsetoptions, issues in later groups named the group before theirs. png-options=bitdepth:1on a grayscale result now turns values below 128
black and the rest white. Before, every gray above black came out white.- With a processing pool, a request that
waits for its turn while its original is still downloading:- Answers
503once it has waitedqueue_timeout. Before, it waited
for a second turn after the download finished. - Answers an error from processing, such as a
415for an image that
can't be decoded, without waiting for a second turn. Before, it could
answer503instead when that wait timed out. - No longer fails with
50360 seconds after its original finished
downloading whenqueue_timeoutplusprocessing_timeoutallows
longer.
- Answers
- S3 credentials whose access key, secret, or session token holds a line
break or NUL are rejected: static ones when the configuration is checked,
and ones from a credential provider when they're fetched, which answers
500and logs which field was invalid. Before, every fetch with them
failed with502, or with404when the secret held one. - A
detectoranchor=smart-facecrop whose detector isn't available in
this build no longer adds a detection to the work the cache estimates for
the image, which the cache uses to choose what to evict and reports as
:estimated_cost_uson[:cache, :write]. - The default Logger logs every
4xxrequest line at:warning. Before, a
4xxfor a failed decode, an input limit, a transform, or an invalid
source logged at:error. - A request with an invalid source has the result
:parser_error, not
:processing_error. Like other parser errors, its
[:request]stop fromImagePipe.run/4has no:error. - The request line for an invalid source or invalid output gives the
reason:reason="invalid source"orreason="invalid output". - A 16-bit original delivered at 8 bits per channel has each value rounded
to the nearest 8-bit level. Before, about a quarter of the values were one
level off, so 65400 became 255 instead of 254. This also applies to
png-options=bitdepth:8withhdr=preserve. - A request that chains many operations, such as a watermark in each of a
dozen groups, no longer crashes the Erlang VM. libvips ran out of stack
rendering the chain. The image is now copied to memory once six
operations have run since its last copy, which keeps the chain short. png-options=palettegives a palette PNG from a 16-bit result, such as
one withhdr=preserve. Without abitdepth, the palette was dropped and
the PNG stayed 16-bit.- With
meta=keepandprofile=srgb,display-p3, oradobe-rgb, the
kept EXIF no longer carries the original's white point and primaries, and
its ColorSpace tag is sRGB only forprofile=srgband Uncalibrated
otherwise. Before, a Display P3 result could keep ColorSpace sRGB from its
original.
Security
- Breaking:
ImagePipe.Source.S3.ContainerCredentialsrejects a
base_urlthat doesn't usehttps, a loopback host, or the ECS or EKS
credential endpoint, as it already did forfull_uri. Before, a
relative_urisent the authorization token to anybase_url. The default
base_urlis unaffected. Change any otherbase_urlto usehttpsor a
loopback host. - Redacting a request path for telemetry, and echoing it in a
400
response, take time in proportion to the path's length. A path with many
wm-src64=segments took seconds to redact, and other crafted 10 KB
paths took tens of milliseconds. Telemetry redacts every request path
before its URL signature is checked, so an unsigned request could cause
this. - Plain-text error responses, such as the
400and the405, now send
X-Content-Type-Options: nosniff. A400body quotes the request
path, so a browser that sniffs content types could otherwise render it as
HTML. - The default Logger and the span tracer cap how much of each error reason
and attribute value they print, so a large term can't produce an
unbounded log line or span. Before, the Logger relied on Logger's own
truncation, which a host can turn off.