Repository navigation
Releases
4.0.0-rc.2
ImageScript 4.0.0-rc.2
The second release candidate for ImageScript 4.0 is ready for evaluation. This is a major release with breaking API
changes from 3.x. Before upgrading, update to the latest 3.x release and address its deprecation warnings, then follow
the migration guide.
What changed
-
Values that were wrapped, clamped or ignored throw: colors have to be integers from -2^31 to 2^32 - 1, also the ones a
fill()function returns (they wrapped to 32 bits; negative ones, which bitwise operators return, keep working); pixel coordinates are truncated without wrapping at 2^32;compressionis an integer 0-9 likeeffort(it was clamped); frame durations that GIF, APNG or WebP can't store throw aRangeErrorlike loop counts (they were cut; unchanged frames merged by the encoders are split at the limit);frame.xOffsetandframe.yOffsetare truncated to integers when set;image.bitmap = viewthrows aTypeErrorfor a view that doesn't start at a multiple of 4 bytes (it was copied, so changes to the view were lost);Colors.gradient()throws for aNaNposition with any number of stops;encodeGIF()of an animation without frames throws like the other encoders. -
Invalid choices throw a
TypeErrornaming the valid ones: the resizemode(anError, thrown once resizing started, soresize({ fit: 'cover' })could move a frame's offsets before failing),composite()'smode,flip(),disposalModeandwrapStyle(RangeErrors; an empty ornullwrapStylewas the default). -
No creation time and no version in encoded files by default: the encoders only write a creation time when
metadata.creationTimeis given, so the same image encodes to the same bytes, and the default software is'ImageScript'without the version. -
Drawing an SVG image that can't be drawn throws: an
hrefthat isn't a data URI or one of theimages, an image that can't be decoded (with the decoding error as itscause), an unsupported data URI type or an embedded SVG that can't be read throw once they are drawn (images in<defs>or hidden ones don't); they drew nothing before. -
renderSVG({ maxLayerPixels })limits the layers SVG rendering uses at once (groups with opacity, masks, filters, ...; 16 bytes per pixel each), so nested ones can't multiply the memory of a small SVG: default4 * maxPixels.renderSVG({ maxImagePixels })limits the raster images an SVG references together (defaultmaxPixels; each was limited on its own before). -
renderText({ maxPixels }), default 16383², counting the outline, checked before anything is allocated.
See the migration guide for the full list of removed and reshaped APIs, behavior
changes, and migration examples.
4.0.0-rc.1
ImageScript 4.0.0-rc.1
The first release candidate for ImageScript 4.0 is ready for evaluation. This is a major release with breaking API
changes from 3.x. Before upgrading, update to the latest 3.x release and address its deprecation warnings, then follow
the migration guide.
What changed
- A more consistent API: methods that take options now use options objects instead of positional arguments.
Resize modes and frame disposal modes are plain strings; color conversion helpers are grouped underColors. - Unified frames and animations:
Framenow contains anImageatframe.image;Animationstores frames in
animation.framesinstead of extendingArray. The separateGIFclass is replaced byAnimation. - Flexible resizing:
resize({ width, height, fit })supports stretching, aspect-preservinginside, padded
contain, and croppedcoverbehavior on images, frames, and animations. - More predictable animation handling:
decode()recognizes animation markers, including one-frame animations;
Image.decode()returns the first frame. Infinite looping is represented byInfinityinstead of-1. - Behavior changes to check:
rotate()now turns clockwise; HSL hue values use degrees; JPEG encoding composites
transparency onto black by default;quantize()dithers by default; and size-changing operations onFramemove
its offsets to keep it positioned in the animation. - Expanded encoding and image operations: single images can encode as GIF, GIF encoders expose color-count and
dithering options, animations gain more image operations, and SVG rendering accepts fonts and embedded images. - Tighter types and validation: fixed-length tuple types, checked frame and gain-map values, and
Promise<Uint8Array<ArrayBuffer>>encoder results improve type safety. The encoder result type requires TypeScript
5.7 or newer.
See the migration guide for the full list of removed and reshaped APIs, behavior
changes, and migration examples.
3.20.2
Fixes
- Loop counts that don't fit the file format are refused with a
RangeErrornaming the format's largest count, instead of wrapping around: a GIF withloopCount: 65536was written with 0, which plays forever (GIF and animated WebP store up to 65535 and 65534, APNG up to 2147483646). This applies toencodeGIF(),encodeWebP()andencodeAPNG()of animations and to the streaming encoders.
3.20.1
Performance
- Drawing on an image with a gain map (an HDR photo) is much faster:
drawBox()anddrawCircle()only recompute the part of the map they cover, instead of the whole map for every call. 400 scanlines over a 1820x1820 Ultra HDR photo take 0.3 s instead of 5.8 s, with the same result. composite()onto or from an image with a gain map is up to twice as fast: 1820x1820 with a blend mode like'add'takes 0.22 s instead of 0.48 s, with the same result. Gain maps stay in sync with the pixels as before.
3.20.0
New
image.quantize({ colors, palette, dither, ditherStrength }): reduces the image to a palette, in place:image.quantize({ colors: 16 })for the 16 colors that best represent it, or a fixed one:image.quantize({ palette: [0x0f380fff, 0x306230ff, 0x8bac0fff, 0x9bbc0fff] })for a Game Boy look.dither: 'floyd-steinberg'(or'atkinson', or'ordered'for a regular pattern) makes gradients look smooth with few colors. Transparent pixels keep their color and don't count towards the palette.Image.renderSVG(svg, { images }): images for the<image>elements by theirhref, without data URIs:Image.renderSVG('<svg ...><image href="logo.png" .../></svg>', { images: { 'logo.png': bytes } }). The values are encoded images orImages; they are decoded only when the SVG uses them. Nothing is read from files or URLs.image.colorMatrix(matrix): mixes the colors of every pixel with a color matrix, like SVG'sfeColorMatrix: greyscale, sepia, tints, swapped channels, color blindness simulation or any mix of them in one pass. Twenty values (four rows of[red, green, blue, alpha, offset], 0 to 1) or nine for just the colors:image.colorMatrix([0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0])swaps red and blue.image.transform(matrix, { origin, expand, mode }): moves every point of the image with an affine transform in one resampling: rotation, scaling, skew, flips and shifts in any mix and around any point. The six values are SVG'smatrix(a b c d e f):image.transform([1, 0, -0.3, 1, 0, 0])slants an image like italics. The image grows to hold the result unlessexpand: false, andmodeis'nearest-neighbor','bilinear'(default) or'bicubic'.
3.19.0
3.19.0
New
image.dropShadow({ x, y, blur, spread, color, expand }): a shadow behind the image, shaped by its alpha. Without an offset it is a glow:image.dropShadow({ blur: 8, spread: 2, color: 0xff2fb9ff }). The image grows to fit it, unlessexpand: false.image.outline({ width, color, expand }): an outline around the image's shape, the sticker effect for cut-out images or an outline for rendered text. It follows the edge's antialiasing, like thestrokeWidthofrenderText()does.Image.linearGradient(width, height, { stops, angle })andImage.radialGradient(width, height, { stops, x, y, radius }): images with a gradient, with the angle like CSS'linear-gradient().stopsare positions and colors likeColors.gradient()takes them, or just the colors, evenly spaced.image.recolor(otherImage): takes the colors from another image of the same size and keeps the alpha. With a gradient it fills a shape, e.g. gradient text:text.recolor(Image.linearGradient(text.width, text.height, { stops: [0x22d3eeff, 0xfb923cff], angle: 90 })).
3.18.0
3.18.0
Changes
-
Resize and SVG scale modes are lowercase names:
'nearest-neighbor','bilinear','bicubic','area'and
'scale','width','height', also as the values ofResizeModesandSVGScaleModes(3.17.0 gave them the old
values). The old values ('RESIZE_BILINEAR',1-3, still the values of the deprecatedImage.RESIZE_*and
Image.SVG_MODE_*) keep working until 4.0:image.resize(256, 256, 'bicubic'). -
Animation.decode()applies the orientation of APNGs and animated WebPs (their eXIf and EXIF chunks, and XMP
in PNGs), likeImage.decode()does (it already did for JPEG XLs).applyOrientation: falsekeeps the stored
frames. Still PNGs decoded byAnimation.decode()are rotated as well now.
New
Animation.decode()takesapplyOrientation,toneMappingandgainMaplikeImage.decode(): JPEG XL
animations can be tone mapped with'clamp', and theirjhgmgain map is read into thegainMapof every frame.resize({ width })andresize({ height })on images, frames and animations: the missing side keeps the aspect
ratio,resize({ width: 256, mode: 'bicubic' }).image.coordinates()yields[x, y]andimage.pixels()yields[x, y, color]for every pixel, row by row.cropCircle({ size: 'contain' | 'cover' }):'contain'(the default, as before) keeps the circle inside the image.Image.renderText(text, { font, size, color, layout, ...style }): the text first, everything else in the options.
layoutisTextLayoutOptionsor aTextLayout:Image.renderText('Hi', { font, size: 32, layout: { maxWidth: 400 } }).Infinityas the loop count of animations that loop forever (new Animation(frames, { loopCount: Infinity }),
and the encoders'). Animations that loop forever still have-1as theirloopCount(also the decoded ones).Animation.webpEncoder(),gifEncoder()andapngEncoder()take the options ofencodeWebP(),encodeGIF()and
encodeAPNG(), plusloopCount: the options are documented once.trim({ color, tolerance })andcolorToAlpha({ color, transparencyThreshold, opacityThreshold }): the color goes into the options, like the other options.GainMap.encode({ quality })likewise.
Deprecated
- Positional optional arguments, the options take the same values:
trim(color, options),colorToAlpha(color, options),GainMap.encode(quality),resize/scale/contain/fit/coverwith amodeargument (useResizeOptions),Frameconstructor andFrame.from()with duration and offsets (useFrameOptions),Animationconstructor withloopCount(useAnimationOptions),Animation.decode(input, onlyExtractFirstFrame),Animation.resize(width, height, mode),Animation.encodeGIF(quality)andencodeAPNG(compression),encodePNG(compression, metadata)andencodePNG(metadata),encodeJPEG(quality),renderSVG(svg, size, mode),composite(source, x, y, options),rotate(angle, resize),sharpen(amount, radius),roundCorners(radius),fisheye(strength),dominantColor(ignoreBlack, ignoreWhite, threshold). All of them still work until 4.0. - The numeric frame disposal ids (
0-3): use the names ('any','keep','previous','background'). Removed in 4.0. ResizeModes.AUTO(andImage.RESIZE_AUTO,Frame.RESIZE_AUTO): pass only the width or the height instead.
Removed in 4.0.- Iterating over an image (
for (const [x, y] of image)) anditerateWithColors(): usecoordinates()and
pixels(). Removed in 4.0. Image.renderText(font, size, text, ...): userenderText(text, { font, size, ... }). Removed in 4.0, as is the
swapped use ofhorizontalAlignandverticalAlignvalues ofTextLayout.-1as a loop count, useInfinity. Removed in 4.0, when animations that loop forever haveInfinity.AnimationEncoderOptions,WebPAnimationEncoderOptions,GIFAnimationEncoderOptionsand
APNGAnimationEncoderOptions: they are the same asAnimationEncodeOptions,WebPAnimationEncodeOptions,
GIFEncodeOptionsandAPNGEncodeOptionswithloopCount, which are all the types there are in 4.0.encode(): useencodePNG(), which takes the same arguments. Removed in 4.0.cropCircle(max, feathering)and themaxoption: usecropCircle({ size: 'contain' | 'cover' }). Removed in 4.0.
Documentation
- Deprecated overloads are tagged in the reference as well: a Deprecated label sits in front of each one, with the replacement named below it, also for methods that have other overloads that aren't deprecated.
3.17.0
3.17.0
New
Image.recolor(color): sets the RGB of every pixel to a color and keeps each pixel's alpha, for silhouettes
and tints:image.recolor(0xffffffff)makes every pixel white, keeping its transparency.AnimationEncoder.frameCount: how many frames were added.AnimationEncoder.framesis deprecated: it's a
number, unlikeAnimation.frames, which is an array. It will be removed in 4.0.Colors: the color helpers moved out ofImageinto their own class, with the same results:
Colors.fromRGBA(),fromRGB(),fromHSLA(),fromHSL(),toRGBA(),toRGB(),toHSLA()(new: a color as
HSLA values) andgradient().ResizeModes,SVGScaleModesandFrameDisposalModes: the constants have classes of their own, with the same
values:image.resize(256, ResizeModes.AUTO, ResizeModes.BICUBIC),Image.renderSVG(svg, 512, SVGScaleModes.WIDTH),
new Frame(w, h, {disposalMode: FrameDisposalModes.BACKGROUND}).
Deprecated
Image.RESIZE_*,Image.SVG_MODE_*andFrame.DISPOSAL_*: useResizeModes.*,SVGScaleModes.*and
FrameDisposalModes.*(Image.RESIZE_AUTObecomesResizeModes.AUTO). The old names keep working and will be
removed in 4.0. They are plain read-only properties now instead of getters.- The
absoluteargument ofopacity(),red(),green(),blue(),lightness(),saturation()(and the
absoluteoption ofAdjustOptions): these methods scale by default; setting a fixed value is removed in 4.0.
For a fixed color, userecolor(). Image.rgbaToColor(),rgbToColor(),hslaToColor(),hslToColor(),rgbaToHSLA(),colorToRGBA(),
colorToRGB()andgradient(): use theColorsmethods of the same role (Colors.fromRGBA()and so on).
Documentation
- imagescript.dev has a new look: the logo's colors, clearer type, signatures and
parameters that are easier to scan, and a sidebar grouped into classes, functions, interfaces and types. Inherited
members are hidden by default (Settings → Inherited shows them), deprecated APIs are tagged and grouped at the end. - Every parameter is described now.
3.16.1
3.16.0
New
- HDR photos (Ultra HDR / JPEG-R JPEGs, as phones take them: an SDR JPEG with a gain map saying how much brighter
each part gets on HDR displays) stay HDR.Image.decodereads the gain map into the newimage.gainMap(a
GainMap: its pixels, decoded only when needed, and its parameters, from ISO 21496-1 or Adobe's hdrgm XMP), and
encodeJPEG()writes it back with both kinds of metadata (Android, Chrome and Apple read them): decoding and
re-encoding a photo keeps the gain map. Before, it was dropped and HDR photos came out SDR only.- The gain map follows the image: resizing,
cover/contain/fit, cropping,region, trimming, rotating,
flipping,fisheyeand the Exif orientation transform it alike (at its own resolution, a quarter of the width
and height in photos from phones). - Drawing keeps it right: what
composite()puts over the image brings its own gain map (or none: no boost there),
blended with the image's by the light of both;drawBox,drawCircleandfillleave no boost where they
draw. Images without a gain map get one when an HDR image is composited onto them. - Color changes (
brightness,contrast,gamma,lightness,red/green/blue, and for maps with a gain
per channel alsosaturation,hueShift,greyscale,sepia) are made to the HDR rendition too, on values
with SDR white as 1 and the highlights above it: what HDR displays don't boost changes like on SDR ones, the
highlights change alike. The inverts leave no boost (the highlights would become the darkest parts);blur
blurs the gain map alike. new GainMap(image, options)makes gain maps (image.gainMap = ...,nullremoves it),GainMap.decode()and
gainMap.encode()read and write one alone.encodeJPEG({ gainMap: { quality, scale } })re-encodes the map
at a quality or size,{ gainMap: false }leaves it out;DecodeOptions.gainMap: falsedoesn't read it.- Files with the gain map cut off (some chat apps and uploads keep the index but drop the map) and broken gain map
metadata decode as before, without one. - AVIFs and JPEG XLs carry gain maps too (AVIF 1.2's tone mapped item, JPEG XL's jhgm box):
Image.decodereads
them,encodeAVIF()andencodeJXL()write them (with the samegainMapoption), so Ultra HDR JPEGs convert to
HDR AVIFs and JPEG XLs a fraction of the size. - iPhone JPEGs carry Apple's own kind of gain map (with its headroom in Apple's XMP or Exif maker note): it's read
too, converted to the same parameters, and written as an Ultra HDR gain map.
- The gain map follows the image: resizing,