images

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 7, 2026 License: BSD-3-Clause Imports: 18 Imported by: 0

README

go-images/images

images — go-images

Docs License Go Status

A pure-Go (no cgo) image-processing library in the style of scikit-image, built on the Go standard library's image, image/color, image/png and image/jpeg packages, with decoding of six further container formats (GIF, WebP, TIFF, BMP, ICO, ICNS) delegated to the shared reference registry go-gfx/gfx/codec — still entirely CGO-free.

Every operation is a pure function: it takes an image and returns a freshly allocated *image.RGBA, never mutating its input.

Why not just use a C library?

The established image stacks all need a native C dependency: ruby-vips wraps libvips, RMagick wraps ImageMagick, and the cgo-backed Go wrappers pull in the same shared libraries. That makes cross-compilation and embedding awkward. go-images/images is CGO=0: it builds and runs identically on every Go target with no system packages, which makes it trivially cross-compilable and embeddable (for example inside go-embedded-ruby).

Pure-Go image libraries already exist (bild, disintegration/imaging); the goal here is a clean, correct, fully test-covered core whose hot pixel kernels run SIMD inner loops generated by go-asmgen (amd64 SSE2, arm64 NEON, s390x z/vector; scalar + multicore on loong64 / ppc64le / riscv64) plus multicore tiling, across the six supported 64-bit targets (amd64, arm64, riscv64, loong64, ppc64le, s390x). See docs/plan-images.md for the roadmap and an honest comparison.

API

import "github.com/go-images/images"

Conversion and I/O:

  • images.ToRGBA(img image.Image) *image.RGBA
  • images.Load(path string) (*image.RGBA, error)
  • images.Save(path string, img image.Image) error — format by extension (.png, .jpg, .jpeg)
  • images.Decode(r io.Reader) (*image.RGBA, error) — format auto-detected (PNG, JPEG, GIF, WebP, TIFF, BMP, ICO, ICNS)
  • images.DecodeBest(r io.Reader, targetSize int) (*image.RGBA, error) — like Decode, but picks the representation nearest targetSize for multi-image containers (ICO, ICNS)
  • images.Encode(w io.Writer, img image.Image, format images.Format) error — images.PNG, images.JPEG
  • images.DecodePartial(r io.Reader) (*image.RGBA, int, error) — an image that may still be arriving: the picture at full size and how many rows of it are real
  • images.DecodePartialFile(path string) (*image.RGBA, int, error) — the same, on a file being written
Decoding a picture that is still arriving

Every standard decoder is all-or-nothing, which for a file still arriving is the same as having nothing. Measured on a real photograph truncated at three fractions, image/jpeg, image/png and image/gif each returned a nil image and an error every time, with three quarters of the picture on disk.

img, rows, err := images.DecodePartial(r)
// draw img's first `rows` rows; err says what stopped the decode

Rows past the count hold whatever the image was allocated with. The error comes back with the picture and is nil only when everything arrived, in which case the count is every row.

Three of the eight formats can answer, through forks of the standard decoders that keep what they decoded: png, jpeg, gif (its first frame). Each refuses some shapes of its own — an interlaced PNG or GIF, a progressive or CMYK JPEG — for reasons stated where the refusal is made.

The other five say so and name themselves. WebP is the one that could not be forked the same way: its frame is a VP8 keyframe, decoded as a whole rather than row by row, so there is no partial state to hand back.

One divergence worth knowing: on a complete file DecodePartial returns the fork's decode, and for a four-component JPEG that differs from Decode — the fork upsamples chroma the way libjpeg does. Decode's standard-library path is untouched.

Operations (each returns a new *image.RGBA):

Point & colour:

  • images.Grayscale(img) — luminance-weighted (Rec. 601)
  • images.Invert(img)
  • images.AdjustBrightness(img, delta) — clamped to [0, 255]
  • images.AdjustContrast(img, factor) — about mid-point 128, clamped
  • images.RGBToHSV(img) / images.HSVToRGB(img) — byte-encoded, round-trip-stable
  • images.OtsuThreshold(img) — Otsu level (matches skimage.filters.threshold_otsu)
  • images.Threshold(img, t) / images.Otsu(img) — binarise on luminance

Filters:

  • images.Convolve(img, images.Kernel{...}) — arbitrary odd kernel, clamp-to-edge
  • images.GaussianBlur(img, sigma) — separable Gaussian
  • images.BoxBlur(img, radius) — separable running-sum mean (matches scipy.ndimage.uniform_filter)
  • images.Median(img, radius) — square median (matches scipy.ndimage.median_filter, mode="nearest")
  • images.UnsharpMask(img, radius, amount) — sharpen via src + amount*(src − blur) (matches skimage.filters.unsharp_mask)
  • images.Sharpen(img) — UnsharpMask(img, 1.0, 1.0)

Edges:

  • images.Sobel(img) — gradient-magnitude edge map (classic integer kernels, on luminance)
  • images.SobelX(img) / images.SobelY(img) — directional Sobel responses (mid-grey = zero gradient)
  • images.Prewitt(img) / images.Scharr(img) / images.SobelMag(img) — normalised gradient magnitude (match skimage.filters.{prewitt,scharr,sobel})
  • images.Laplacian(img) — discrete Laplacian (matches skimage.filters.laplace, ksize 3)
  • images.Canny(img, sigma, low, high) — binary Canny edge map (Gaussian → Sobel → bilinear NMS → hysteresis)

Morphology (grayscale square element, also binary on 0/255 images):

  • images.Erode(img, r) / images.Dilate(img, r) — local min / max
  • images.Open(img, r) / images.Close(img, r) — erode→dilate / dilate→erode

Geometry:

  • images.Resize(img, w, h, mode) — images.NearestNeighbor, images.Bilinear, images.Area, images.Bicubic or images.Lanczos. The resampling kernels live once, in the shared foundation go-gfx/gfx/resample; Resize delegates to them over a zero-copy raster view, so nothing is duplicated here.

    • Area is area/box averaging (PIL Image.BOX, OpenCV INTER_AREA; at integer ratios, scikit-image downscale_local_mean). The mode to reduce with — nearest keeps one source pixel in sixteen when shrinking by four, and bilinear never looks at more than four neighbours however far the image is shrunk.
    • Bicubic (Catmull-Rom) and Lanczos (a = 3) are Pillow's BICUBIC / LANCZOS: their footprint widens with the reduction factor, so they antialias a reduction and sharpen an enlargement. Their colour channels are filtered in premultiplied-alpha space, so a transparent pixel's colour never bleeds into the visible edge of a cut-out.

    NearestNeighbor, Bilinear and Area are proven byte-for-byte identical to the kernels this library previously carried (see resize_dedup_control_test.go).

  • images.FlipHorizontal(img) / images.FlipVertical(img) — numpy.fliplr / flipud

  • images.Rotate90(img) / images.Rotate180(img) / images.Rotate270(img) — numpy.rot90

  • images.Transpose(img) / images.Transverse(img) — reflections across the main and anti-diagonal (PIL Image.TRANSPOSE / Image.TRANSVERSE)

  • images.Crop(img, image.Rect(x0, y0, x1, y1))

  • images.ExifTranspose(img, orientation) and the auto-orienting decoders images.DecodeExifTranspose(r) / images.LoadExifTranspose(path) — apply the eight EXIF orientations so a photo displays the right way up, the geometry of Pillow's ImageOps.exif_transpose. The orientation tag is read from a JPEG's APP1/Exif segment (or a bare TIFF) with a minimal in-package reader; an absent or invalid tag is treated as "normal" and the image is returned unrotated.

  • images.Rotate(img, angleDegrees, resize) — arbitrary-angle rotation about the centre, bilinear, matching skimage.transform.rotate (byte-for-byte; resize=true grows the canvas to fit, sized exactly as scikit-image does)

  • images.Warp(img, inv, outW, outH, interp, mode, cval) — general affine resampling, matching skimage.transform.warp (clip=False). inv is an images.Affine mapping output (col,row) to input (col,row) — the inverse of the transform being applied, exactly as scikit-image's warp takes an inverse map. interp is InterpNearest (order 0) or InterpBilinear (order 1); mode is BorderConstant (fill with cval) or BorderEdge. Build transforms with images.Identity/Translation/Scaling/Rotation and compose them with .Then(...); invert with .Invert().

Example
src, err := images.Load("in.png")
if err != nil {
    log.Fatal(err)
}
gray := images.Grayscale(src)
blurred, err := images.GaussianBlur(gray, 2.0)
if err != nil {
    log.Fatal(err)
}
if err := images.Save("out.png", blurred); err != nil {
    log.Fatal(err)
}

Performance

A rigorous parity benchmark against scikit-image 0.26 / scipy 1.18 and OpenCV 4.13 lives in BENCHMARKS.md (reproducible harness in benchmarks/). Headline, single-thread, core-for-core on an Apple M4 Max:

  • Wins vs scikit-image: box blur 1.8–2.6× (O(1) running-window sum), RGB→HSV ~4.8× (fused pass), flip ~2×, Gaussian 1.0–1.3× (the former Gaussian loss is now closed by the SIMD axpy separable convolution), and Sobel 1.02–1.06× (the former recompute-per-pixel loss is now closed by a cached luminance plane and a branch-free interior run).
  • Morphology — now at scikit-image parity. The O(radius) fold was replaced with the O(1) van Herk / Gil-Werman running min/max, so erode/dilate are flat in radius and reach parity → 1.02× of scikit-image at 4096² single-thread (≈0.8× at small radius is a pure constant factor → SIMD).
  • Gaps vs scikit-image: the residual single-thread gap (small-radius morphology, and Rotate90/Crop/Otsu against a bare numpy view/copy or histogram, which is not comparable per-pixel work) is now a pure constant factor; closing it with go-asmgen SIMD across all six arches is BENCHMARKS.md action item C.
  • Multicore: with all cores go-images is faster than single-threaded scikit-image on every op (morphology now 4.8–7.8×, Sobel now 4.2–4.8×), but OpenCV's O(1)+SIMD morphology is still far ahead — cores don't replace SIMD.

Box blur and grayscale morphology match SciPy bit-for-bit; Gaussian within one LSB; every SIMD kernel is validated against its scalar oracle. See BENCHMARKS.md for the full tables, methodology and action items, and docs/perf.md for the historical SIMD notes.

License

BSD-3-Clause. See LICENSE.

Documentation

Overview

Package images is a pure-Go (cgo-free) image-processing library in the style of scikit-image, built on the Go standard library's image, image/color, image/png and image/jpeg packages.

Unlike the established options in the Ruby world — ruby-vips (libvips) and RMagick (ImageMagick) — and unlike cgo-backed Go wrappers, this library has no native C dependency: it is CGO=0 and builds and runs identically on every Go target. That makes it trivially cross-compilable and embeddable (for example inside go-embedded-ruby), at the cost — for now — of the hand-tuned SIMD that libvips/ImageMagick rely on. A later phase will close that gap with SIMD kernels generated by go-asmgen across the six supported 64-bit targets. See docs/plan-images.md for the roadmap and an honest comparison with the existing pure-Go libraries (bild, disintegration/imaging).

Operations are pure functions: each takes an image and returns a freshly allocated *image.RGBA, never mutating its input. Use ToRGBA to convert an arbitrary image.Image to the *image.RGBA the operations consume, and the I/O helpers (Load, Save, Decode, Encode). Decode auto-detects eight container formats — PNG and JPEG via the standard library, and GIF, WebP, TIFF, BMP, ICO and ICNS via the shared reference registry github.com/go-gfx/gfx/codec; Encode writes PNG and JPEG.

Phase 0 implements Grayscale, Invert, Resize (nearest-neighbour, bilinear, area/box, and the high-quality bicubic and Lanczos modes, all delegated to the shared go-gfx/gfx/resample foundation), Convolve (arbitrary odd-sized kernels with edge clamping), GaussianBlur (separable), AdjustBrightness and AdjustContrast.

Phase 1 adds edge detection — Sobel (gradient magnitude) with SobelX/SobelY directional responses, the normalised Prewitt, Scharr and SobelMag operators and the Laplacian (matching skimage.filters.{prewitt,scharr,sobel,laplace}), and the full Canny detector (Gaussian, Sobel, bilinear non-maximum suppression, hysteresis); the filters BoxBlur (separable running-sum mean, matching scipy.ndimage.uniform_filter), Median (square median, matching scipy.ndimage.median_filter), and UnsharpMask/Sharpen (matching skimage.filters.unsharp_mask); the geometric transforms FlipHorizontal, FlipVertical, Rotate90/Rotate180/Rotate270 (numpy.fliplr/flipud/rot90) and Crop; the colour conversions RGBToHSV/HSVToRGB; and thresholding via OtsuThreshold/Threshold/Otsu (matching skimage.filters.threshold_otsu).

Phase 2 adds grayscale morphology over a square structuring element: Erode and Dilate (per-channel local min/max, matching scipy.ndimage.grey_erosion and grey_dilation) and the derived Open and Close. On binary 0/255 images these reduce to ordinary binary morphology.

docs/perf.md reports honest go-images-vs-scikit-image/SciPy benchmarks and correctness checks for the hot operations.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AdjustBrightness

func AdjustBrightness(img image.Image, delta float64) *image.RGBA

AdjustBrightness returns a copy of img with delta added to the R, G and B channels, clamped to [0, 255]. Alpha is preserved.

func AdjustBrightnessRaster

func AdjustBrightnessRaster(src *raster.Image, delta float64) *raster.Image

AdjustBrightnessRaster is the raster-typed façade for AdjustBrightness.

func AdjustContrast

func AdjustContrast(img image.Image, factor float64) *image.RGBA

AdjustContrast returns a copy of img with the R, G and B channels scaled about the mid-point (128) by factor, clamped to [0, 255]. A factor of 1 leaves the image unchanged; values above 1 increase contrast, values in [0, 1) reduce it. Alpha is preserved.

func AdjustContrastRaster

func AdjustContrastRaster(src *raster.Image, factor float64) *raster.Image

AdjustContrastRaster is the raster-typed façade for AdjustContrast.

func AsRGBA

func AsRGBA(r *raster.Image) *image.RGBA

AsRGBA returns an *image.RGBA that shares r's pixel buffer without copying.

go-gfx's raster.Image (the shared 2-D substrate this library sits on top of) and the standard library's image.RGBA use the identical physical layout: a densely packed, origin-anchored, row-major slice of four bytes (R, G, B, A) per pixel with stride 4*W and no row padding. The two therefore alias one backing array. The only nominal difference is the alpha model — raster.Image is straight (non-premultiplied) whereas image.RGBA documents its bytes as premultiplied — but every operation in this package treats the bytes as straight per-channel values and preserves the alpha channel unchanged, so the aliased view is processed exactly as raster's own straight bytes would be. Writing through either view is visible through the other.

func AsRaster

func AsRaster(img *image.RGBA) *raster.Image

AsRaster returns a raster.Image viewing img's pixels. When img is densely packed and origin-anchored (Rect.Min at the origin and Stride == 4*width) the returned raster shares img's backing array with no copy, the exact inverse of AsRGBA; otherwise — a sub-image, a padded stride, or a non-origin rectangle — the pixels are compacted into a freshly allocated, tightly packed buffer so the result always satisfies raster.Image's dense, origin-anchored invariant.

func BoxBlur

func BoxBlur(img image.Image, radius int) (*image.RGBA, error)

BoxBlur returns img blurred by a square averaging filter of the given radius: every output pixel is the mean of the (2*radius+1) by (2*radius+1) source neighbourhood centred on it, computed independently per R, G and B channel (alpha preserved). Borders use clamp-to-edge addressing, matching scipy.ndimage.uniform_filter with mode="nearest" and size 2*radius+1. The filter is separable and evaluated with a running window sum, so its cost is independent of the radius. It returns an error if radius is not positive.

func BoxBlurRaster

func BoxBlurRaster(src *raster.Image, radius int) (*raster.Image, error)

BoxBlurRaster is the raster-typed façade for BoxBlur; it propagates BoxBlur's error for a non-positive radius.

func Canny

func Canny(img image.Image, sigma, low, high float64) (*image.RGBA, error)

Canny returns the binary Canny edge map of img: white (255,255,255) edges on an opaque black background. It implements the classic Canny pipeline, matching the algorithm of skimage.feature.canny:

  1. smooth the Rec. 601 luminance with a Gaussian of standard deviation sigma (clamp-to-edge borders);
  2. estimate gradients with the Sobel operator; the edge strength is the gradient norm;
  3. thin to 1-pixel ridges by non-maximum suppression with bilinear interpolation along the gradient direction;
  4. link edges by hysteresis: keep every ridge pixel with magnitude >= high, plus every ridge pixel with magnitude >= low that is 8-connected to a kept one.

low and high are absolute thresholds on the Sobel gradient magnitude (the smoothed luminance is in [0,255], so the magnitudes are on that scale). It returns an error if sigma is not positive, if either threshold is negative, or if high < low.

func CannyRaster

func CannyRaster(src *raster.Image, sigma, low, high float64) (*raster.Image, error)

CannyRaster is the raster-typed façade for Canny; it propagates Canny's error for a non-positive sigma, a negative threshold, or high < low.

func Close

func Close(img image.Image, radius int) (*image.RGBA, error)

Close returns the morphological closing of img (dilation followed by erosion). Closing fills small dark features smaller than the structuring element. It returns an error if radius is not positive.

func CloseRaster

func CloseRaster(src *raster.Image, radius int) (*raster.Image, error)

CloseRaster is the raster-typed façade for Close; it propagates Close's error for a non-positive radius.

func Convolve

func Convolve(img image.Image, k Kernel) (*image.RGBA, error)

Convolve returns img convolved with k, using clamp-to-edge addressing at the borders. The R, G and B channels are convolved and clamped to [0, 255]; alpha is preserved. It returns an error if k has non-positive or even dimensions, or if the length of k.Weights does not match Width*Height.

func ConvolveRaster

func ConvolveRaster(src *raster.Image, k Kernel) (*raster.Image, error)

ConvolveRaster is the raster-typed façade for Convolve; it propagates Convolve's error for a kernel with non-positive, even, or mismatched dimensions.

func Crop

func Crop(img image.Image, r image.Rectangle) (*image.RGBA, error)

Crop returns the rectangular region r of img as a new image anchored at the origin. r is interpreted in the coordinate system of img converted to RGBA (origin at the top-left). It returns an error if r is empty or extends outside the image bounds.

func CropRaster

func CropRaster(src *raster.Image, r image.Rectangle) (*raster.Image, error)

CropRaster is the raster-typed façade for Crop; it propagates Crop's error for an empty rectangle or one extending outside the image bounds.

func Decode

func Decode(r io.Reader) (*image.RGBA, error)

Decode reads an image from r, auto-detecting the container format from its magic bytes, and returns it converted to *image.RGBA.

Eight formats are recognised. PNG and JPEG are decoded by the standard library exactly as before — this path is byte-for-byte unchanged. The six additional formats (GIF, WebP, TIFF, BMP, ICO, ICNS) are delegated to the shared reference registry github.com/go-gfx/gfx/codec, which reimplements no decoder and hands each container to a battle-tested pure-Go (CGO-free) library. For the multi-representation containers (ICO, ICNS) the largest representation is returned; use DecodeBest to target a pixel size.

Alpha convention: every source is brought into this package under the same premultiplied-at-input convention that ToRGBA already applies to any non-*image.RGBA source. codec returns straight (non-premultiplied) alpha, so its output is routed back through ToRGBA to premultiply, keeping the whole library's decoded bytes consistent regardless of container format.

func DecodeBest

func DecodeBest(r io.Reader, targetSize int) (*image.RGBA, error)

DecodeBest is Decode with a target pixel size for the multi-representation containers (ICO, ICNS): among their stored representations it selects the one whose longer side is the smallest that is still at least targetSize, falling back to the largest when none reaches it. A targetSize <= 0 selects the largest, making it identical to Decode. targetSize is ignored for single-image formats. The result is a premultiplied *image.RGBA, exactly as from Decode.

func DecodeExifTranspose

func DecodeExifTranspose(r io.Reader) (*image.RGBA, error)

DecodeExifTranspose decodes an image from r and returns it already re-oriented according to its embedded EXIF orientation tag, as a premultiplied *image.RGBA. It is the auto-orienting counterpart of Decode: an image with no EXIF orientation (or a non-JPEG/TIFF source) is returned unrotated. This is the equivalent of Pillow's ImageOps.exif_transpose applied at decode time.

func DecodePartial

func DecodePartial(r io.Reader) (*image.RGBA, int, error)

DecodePartial reads an image from r that may still be arriving, and returns what of it is real: the picture at its full declared size, and the number of pixel rows that are COMPLETE counting from the top. Rows past the count hold whatever the image was allocated with, so a caller draws the first rows and nothing else.

The error that stopped the decode comes back with them, and is nil only when the whole image arrived — in which case the count is every row.

⛔ Every standard decoder is all-or-nothing, which for a file still arriving is the same as having nothing. Measured on a real photograph truncated at three fractions, image/jpeg, image/png and image/gif each returned a nil image and an error EVERY time, with three quarters of the picture on disk. This is why the three forks behind this call exist.

Three of the eight formats Decode recognises can answer, and each refuses some shapes of its own — an interlaced PNG or GIF, a progressive or CMYK JPEG — for reasons written where the refusal is made:

  • PNG, by github.com/go-images/png
  • JPEG, by github.com/go-images/jpeg
  • GIF, by github.com/go-images/gif (its first frame)

The other five (WebP, TIFF, BMP, ICO, ICNS) report that they cannot. WebP is the one that could not simply be forked the same way: its frame is a VP8 keyframe, decoded as a whole rather than row by row, so there is no partial state to hand back.

⛔ One divergence from Decode, stated rather than hidden. On a COMPLETE file this returns the FORK's decode, and for a four-component JPEG that differs: the fork upsamples chroma the way libjpeg does, where the standard library repeats each sample. See github.com/go-images/jpeg. Decode's standard-library path is untouched, so anything relying on its exact bytes still gets them.

func DecodePartialFile

func DecodePartialFile(path string) (*image.RGBA, int, error)

DecodePartialFile is DecodePartial on a path, for the common case of watching a file grow on disk.

func Dilate

func Dilate(img image.Image, radius int) (*image.RGBA, error)

Dilate returns the grayscale morphological dilation of img: the per-channel local maximum over a square structuring element. See Erode for borders, alpha and the radius rule.

func DilateRaster

func DilateRaster(src *raster.Image, radius int) (*raster.Image, error)

DilateRaster is the raster-typed façade for Dilate; it propagates Dilate's error for a non-positive radius.

func Encode

func Encode(w io.Writer, img image.Image, format Format) error

Encode writes img to w in the given format.

func Erode

func Erode(img image.Image, radius int) (*image.RGBA, error)

Erode returns the grayscale morphological erosion of img with a square structuring element of the given radius: every output channel is the minimum of the (2*radius+1) by (2*radius+1) source neighbourhood. Borders use clamp-to-edge addressing; alpha is preserved. The operation is separable (matching a square footprint in scipy.ndimage.grey_erosion). On a binary image (0/255) this is ordinary binary erosion. It returns an error if radius is not positive.

func ErodeRaster

func ErodeRaster(src *raster.Image, radius int) (*raster.Image, error)

ErodeRaster is the raster-typed façade for Erode; it propagates Erode's error for a non-positive radius.

func ExifTranspose

func ExifTranspose(img image.Image, o Orientation) *image.RGBA

ExifTranspose returns a copy of img re-oriented so that it displays the right way up, applying the geometric transform that the EXIF orientation o calls for. It mirrors Pillow's ImageOps.exif_transpose geometry:

1 identity          2 flip horizontal    3 rotate 180     4 flip vertical
5 transpose         6 rotate 90 CW       7 transverse     8 rotate 90 CCW

Orientations 5-8 swap the width and height. Any value outside 1-8 (including a zero Orientation) is treated as OrientationNormal and returns an unrotated RGBA copy, matching Pillow's lenient handling of an absent or invalid tag.

func FlipHorizontal

func FlipHorizontal(img image.Image) *image.RGBA

FlipHorizontal returns a copy of img mirrored left-to-right (column x of the width-w source becomes column w-1-x). It matches numpy.fliplr.

func FlipHorizontalRaster

func FlipHorizontalRaster(src *raster.Image) *raster.Image

FlipHorizontalRaster is the raster-typed façade for FlipHorizontal.

func FlipVertical

func FlipVertical(img image.Image) *image.RGBA

FlipVertical returns a copy of img mirrored top-to-bottom (row y of the height-h source becomes row h-1-y). It matches numpy.flipud.

func FlipVerticalRaster

func FlipVerticalRaster(src *raster.Image) *raster.Image

FlipVerticalRaster is the raster-typed façade for FlipVertical.

func GaussianBlur

func GaussianBlur(img image.Image, sigma float64) (*image.RGBA, error)

GaussianBlur returns img blurred by a Gaussian of standard deviation sigma, implemented as a separable convolution with clamp-to-edge borders. It returns an error if sigma is not positive.

func GaussianBlurRaster

func GaussianBlurRaster(src *raster.Image, sigma float64) (*raster.Image, error)

GaussianBlurRaster is the raster-typed façade for GaussianBlur; it propagates GaussianBlur's error for a non-positive sigma.

func Grayscale

func Grayscale(img image.Image) *image.RGBA

Grayscale returns a copy of img with every pixel replaced by its luminance-weighted gray value (Rec. 601 coefficients). Alpha is preserved.

func GrayscaleRaster

func GrayscaleRaster(src *raster.Image) *raster.Image

GrayscaleRaster is the raster-typed façade for Grayscale.

func HSVToRGB

func HSVToRGB(img image.Image) *image.RGBA

HSVToRGB inverts RGBToHSV: it interprets each pixel's first three channels as byte-encoded H, S, V and returns the corresponding R, G, B. Alpha is preserved.

func HSVToRGBRaster

func HSVToRGBRaster(src *raster.Image) *raster.Image

HSVToRGBRaster is the raster-typed façade for HSVToRGB.

func Invert

func Invert(img image.Image) *image.RGBA

Invert returns a copy of img with the R, G and B channels negated. Alpha is preserved.

func InvertRaster

func InvertRaster(src *raster.Image) *raster.Image

InvertRaster is the raster-typed façade for Invert.

func Laplacian

func Laplacian(img image.Image) *image.RGBA

Laplacian returns the discrete Laplacian edge map of img, matching skimage.filters.laplace with ksize=3 (the kernel [0,-1,0; -1,4,-1; 0,-1,0] applied to the luminance plane). The signed second-derivative response is offset by 128 so a flat region is mid-grey, written to R, G and B and clamped to [0,255]; alpha is preserved and borders use clamp-to-edge addressing. Being a second-derivative operator it highlights intensity curvature (lines, spots, zero-crossings) rather than step edges.

func LaplacianRaster

func LaplacianRaster(src *raster.Image) *raster.Image

LaplacianRaster is the raster-typed façade for Laplacian.

func Load

func Load(path string) (*image.RGBA, error)

Load reads and decodes the image at path, returning it as *image.RGBA. The format is auto-detected from the file contents.

func LoadExifTranspose

func LoadExifTranspose(path string) (*image.RGBA, error)

LoadExifTranspose reads the image at path and returns it re-oriented per its EXIF orientation tag, as a premultiplied *image.RGBA. It is the auto-orienting counterpart of Load.

func Median

func Median(img image.Image, radius int) (*image.RGBA, error)

Median returns img filtered by a square median of the given radius: every output channel is the median of the (2*radius+1) by (2*radius+1) source neighbourhood, computed independently per R, G and B (alpha preserved), with clamp-to-edge addressing. It matches scipy.ndimage.median_filter with a (2*radius+1)-square footprint and mode="nearest". The median is robust to outliers, so it removes salt-and-pepper noise while preserving edges far better than a linear blur. It returns an error if radius is not positive.

func MedianRaster

func MedianRaster(src *raster.Image, radius int) (*raster.Image, error)

MedianRaster is the raster-typed façade for Median; it propagates Median's error for a non-positive radius.

func Open

func Open(img image.Image, radius int) (*image.RGBA, error)

Open returns the morphological opening of img (erosion followed by dilation with the same square structuring element). Opening removes small bright features smaller than the element while preserving overall shape. It returns an error if radius is not positive.

func OpenRaster

func OpenRaster(src *raster.Image, radius int) (*raster.Image, error)

OpenRaster is the raster-typed façade for Open; it propagates Open's error for a non-positive radius.

func Otsu

func Otsu(img image.Image) *image.RGBA

Otsu is a convenience wrapper that thresholds img at the level chosen by Otsu's method (equivalent to Threshold(img, OtsuThreshold(img))).

func OtsuRaster

func OtsuRaster(src *raster.Image) *raster.Image

OtsuRaster is the raster-typed façade for Otsu.

func OtsuThreshold

func OtsuThreshold(img image.Image) uint8

OtsuThreshold returns the gray level in [0, 255] computed by Otsu's method on img's Rec. 601 luminance histogram: the level that maximises the between-class variance of the two pixel populations split at it. It matches the value returned by skimage.filters.threshold_otsu on a 256-bin histogram. Pass the result to Threshold (foreground = luminance strictly greater than it).

func Prewitt

func Prewitt(img image.Image) *image.RGBA

Prewitt returns the Prewitt gradient-magnitude edge map of img. The operator is the separable 3x3 Prewitt kernel applied to each pixel's Rec. 601 luminance; the magnitude sqrt((gx^2+gy^2)/2) is written to the R, G and B channels as a grayscale edge image (alpha preserved). It mirrors skimage.filters.prewitt: the directional kernels are normalised so each axis kernel's absolute weights sum to one, and clamp-to-edge addressing reproduces skimage's default reflect border for a 3-tap kernel.

func PrewittRaster

func PrewittRaster(src *raster.Image) *raster.Image

PrewittRaster is the raster-typed façade for Prewitt.

func RGBToHSV

func RGBToHSV(img image.Image) *image.RGBA

RGBToHSV returns a copy of img with each pixel's R, G, B replaced by a byte-encoded H, S, V triple: H is the hue mapped from [0,360) to [0,255], S and V are mapped from [0,1] to [0,255]. Alpha is preserved. HSVToRGB inverts the mapping (within rounding). The encoding keeps the result inside the same RGBA-backed representation the rest of the pipeline uses.

func RGBToHSVRaster

func RGBToHSVRaster(src *raster.Image) *raster.Image

RGBToHSVRaster is the raster-typed façade for RGBToHSV.

func Resize

func Resize(img image.Image, w, h int, mode ResizeMode) (*image.RGBA, error)

Resize returns img scaled to w by h pixels using the given mode. It returns an error if w or h is not positive.

The resampling kernels live once, in go-gfx's resample package (the shared 2-D foundation this library sits on); Resize delegates to them over a zero-copy raster view of the source so no kernel is duplicated here. NearestNeighbor, Bilinear and Area map to resample's Nearest, Bilinear and Box filtered in straight-alpha space — proven byte-for-byte identical to the kernels this library used to run (see resize_dedup_control_test.go) — while Bicubic and Lanczos filter the colour channels in premultiplied-alpha space.

func ResizeRaster

func ResizeRaster(src *raster.Image, w, h int, mode ResizeMode) (*raster.Image, error)

ResizeRaster scales src to w by h pixels using mode, accepting and returning go-gfx's raster.Image so callers already working in the shared substrate do not have to round-trip through image.RGBA. It is exactly Resize evaluated over an aliased view of src: Resize's freshly allocated destination is itself dense and origin-anchored, so both the input and the output are aliased with no extra copy, and the resulting pixel bytes are identical to those Resize produces for the same source. It returns an error if w or h is not positive, or for an unknown mode.

func Rotate

func Rotate(img image.Image, angle float64, resize bool) *image.RGBA

Rotate returns img rotated by angle degrees counter-clockwise about its centre, reproducing skimage.transform.rotate with bilinear interpolation, the "constant" border and a fill value of zero. When resize is false the output keeps the input's dimensions (corners may be clipped); when true the output grows to contain the whole rotated image, exactly as scikit-image sizes it.

It matches scikit-image's rotate with clip=False: the interpolated result is not clamped to the input's value range, so a border pixel fades toward the fill value rather than being pinned to the input's global minimum. (Its clip=True default clamps every channel to the image-wide [min, max], which pins a fade-to-black border up to the minimum — the less useful behaviour.)

func Rotate90

func Rotate90(img image.Image) *image.RGBA

Rotate90 returns img rotated 90 degrees counter-clockwise (matching numpy.rot90 with k=1). A w-by-h image becomes h-by-w.

func Rotate90Raster

func Rotate90Raster(src *raster.Image) *raster.Image

Rotate90Raster is the raster-typed façade for Rotate90.

func Rotate180

func Rotate180(img image.Image) *image.RGBA

Rotate180 returns img rotated 180 degrees (numpy.rot90 with k=2). The dimensions are unchanged.

func Rotate180Raster

func Rotate180Raster(src *raster.Image) *raster.Image

Rotate180Raster is the raster-typed façade for Rotate180.

func Rotate270

func Rotate270(img image.Image) *image.RGBA

Rotate270 returns img rotated 90 degrees clockwise, i.e. 270 degrees counter-clockwise (numpy.rot90 with k=3). A w-by-h image becomes h-by-w.

func Rotate270Raster

func Rotate270Raster(src *raster.Image) *raster.Image

Rotate270Raster is the raster-typed façade for Rotate270.

func Save

func Save(path string, img image.Image) error

Save encodes img and writes it to path, choosing the format from the file extension: ".png" for PNG and ".jpg" or ".jpeg" for JPEG (case-insensitive). It returns an error for any other extension.

func Scharr

func Scharr(img image.Image) *image.RGBA

Scharr returns the Scharr gradient-magnitude edge map of img, matching skimage.filters.scharr. The Scharr smoothing triple (0.1875, 0.625, 0.1875) gives the best rotational symmetry of the Sobel/Prewitt/Scharr family. See Prewitt for the luminance, magnitude and border conventions.

func ScharrRaster

func ScharrRaster(src *raster.Image) *raster.Image

ScharrRaster is the raster-typed façade for Scharr.

func Sharpen

func Sharpen(img image.Image) *image.RGBA

Sharpen returns a sharpened copy of img with sensible defaults: an unsharp mask with radius 1.0 and amount 1.0, i.e. it adds back the full single-pixel- scale detail layer. For finer control over the scale or strength use UnsharpMask directly.

func SharpenRaster

func SharpenRaster(src *raster.Image) *raster.Image

SharpenRaster is the raster-typed façade for Sharpen.

func Sobel

func Sobel(img image.Image) *image.RGBA

Sobel returns the Sobel gradient-magnitude edge map of img. The operator is applied to each pixel's Rec. 601 luminance with clamp-to-edge borders; the magnitude is clamped to [0, 255] and written to the R, G and B channels, producing a grayscale edge image. Alpha is preserved. Strong intensity transitions appear bright, flat regions dark.

func SobelMag

func SobelMag(img image.Image) *image.RGBA

SobelMag returns the normalised Sobel gradient-magnitude edge map of img using the scikit-image convention (sqrt((gx^2+gy^2)/2) on luminance scaled to [0,1]), matching skimage.filters.sobel. It differs from Sobel, which uses the classic integer kernels and the unnormalised magnitude sqrt(gx^2+gy^2); SobelMag shares the one definition used by Prewitt and Scharr so the edge family is directly comparable to scikit-image.

func SobelMagRaster

func SobelMagRaster(src *raster.Image) *raster.Image

SobelMagRaster is the raster-typed façade for SobelMag.

func SobelRaster

func SobelRaster(src *raster.Image) *raster.Image

SobelRaster is the raster-typed façade for Sobel.

func SobelX

func SobelX(img image.Image) *image.RGBA

SobelX returns the horizontal Sobel response of img: an estimate of the left-to-right intensity derivative of each pixel's luminance. The signed response is scaled and offset so a zero gradient is mid-grey (128), a rising edge brighter and a falling edge darker, clamped to [0, 255] and written to R, G and B. Alpha is preserved; borders use clamp-to-edge addressing.

func SobelXRaster

func SobelXRaster(src *raster.Image) *raster.Image

SobelXRaster is the raster-typed façade for SobelX.

func SobelY

func SobelY(img image.Image) *image.RGBA

SobelY returns the vertical Sobel response of img: an estimate of the top-to-bottom intensity derivative of each pixel's luminance, with the same scaling, offset and addressing conventions as SobelX. Alpha is preserved.

func SobelYRaster

func SobelYRaster(src *raster.Image) *raster.Image

SobelYRaster is the raster-typed façade for SobelY.

func Threshold

func Threshold(img image.Image, t uint8) *image.RGBA

Threshold returns a binary image: every pixel of img whose Rec. 601 luminance is strictly greater than t becomes white, every other pixel black. Alpha is preserved. Combine with OtsuThreshold for an automatically chosen level.

func ThresholdRaster

func ThresholdRaster(src *raster.Image, t uint8) *raster.Image

ThresholdRaster is the raster-typed façade for Threshold.

func ToRGBA

func ToRGBA(img image.Image) *image.RGBA

ToRGBA returns img as an *image.RGBA. If img is already an *image.RGBA whose bounds start at the origin, it is returned unchanged; otherwise the pixels are copied (and, when necessary, colour-converted) into a freshly allocated origin-anchored *image.RGBA of the same dimensions.

func Transpose

func Transpose(img image.Image) *image.RGBA

Transpose returns img reflected across its main diagonal (top-left to bottom-right): pixel (x, y) becomes (y, x). A w-by-h image becomes h-by-w. It matches PIL Image.TRANSPOSE and is the geometry of EXIF orientation 5.

func Transverse

func Transverse(img image.Image) *image.RGBA

Transverse returns img reflected across its anti-diagonal (top-right to bottom-left): pixel (x, y) becomes (h-1-y, w-1-x). A w-by-h image becomes h-by-w. It matches PIL Image.TRANSVERSE and is the geometry of EXIF orientation 7.

func UnsharpMask

func UnsharpMask(img image.Image, radius, amount float64) (*image.RGBA, error)

UnsharpMask returns a sharpened copy of img using the unsharp-masking technique: dst = clamp(src + amount*(src - blurred)), where blurred is the Gaussian blur of img with standard deviation radius. The R, G and B channels are processed independently and alpha is preserved. It matches skimage.filters.unsharp_mask applied per channel.

radius controls the scale of the detail recovered (the Gaussian sigma) and must be positive; amount scales how strongly that detail is added back: 0 leaves the image unchanged, typical sharpening uses values around 0.5–2, and negative values soften. It returns an error if radius is not positive.

func UnsharpMaskRaster

func UnsharpMaskRaster(src *raster.Image, radius, amount float64) (*raster.Image, error)

UnsharpMaskRaster is the raster-typed façade for UnsharpMask; it propagates UnsharpMask's error for a non-positive radius.

func Warp

func Warp(img image.Image, inv Affine, outW, outH int, interp Interp, mode BorderMode, cval float64) *image.RGBA

Warp resamples img through the inverse coordinate map inv onto an outW-by-outH output, matching skimage.transform.warp. inv maps an output pixel's (col, row) to the input (col, row) to sample, so it is the inverse of the geometric transform being applied to the image (exactly as scikit-image's warp takes an inverse map). interp selects nearest or bilinear sampling; mode and cval select the out-of-bounds behaviour. cval fills the R, G and B channels outside the image; the alpha channel is filled with 0 there, so out-of-bounds regions are transparent regardless of cval.

Types

type Affine

type Affine struct {
	A, B, C, D, E, F float64
}

Affine is a 2-D affine transform. It maps a point (x, y) — with x the column and y the row, matching scikit-image's coordinate order — to

x' = A*x + B*y + C
y' = D*x + E*y + F

which is the top two rows of the homogeneous 3x3 matrix whose bottom row is (0, 0, 1).

func Identity

func Identity() Affine

Identity is the affine transform that leaves every point unchanged.

func Rotation

func Rotation(theta float64) Affine

Rotation returns a transform that rotates a point about the origin by theta radians, using scikit-image's convention ([[cos, -sin], [sin, cos]] acting on (x, y)).

func Scaling

func Scaling(sx, sy float64) Affine

Scaling returns a transform that scales x by sx and y by sy about the origin.

func Translation

func Translation(tx, ty float64) Affine

Translation returns a transform that shifts a point by (tx, ty).

func (Affine) Apply

func (t Affine) Apply(x, y float64) (float64, float64)

Apply maps the point (x, y) through the transform.

func (Affine) Invert

func (t Affine) Invert() Affine

Invert returns the inverse transform. It panics only for a singular matrix (zero determinant), which no rotation, translation, or non-degenerate scaling produces.

func (Affine) Then

func (t Affine) Then(u Affine) Affine

Then returns the composition that applies t first and then u: the result maps p to u.Apply(t.Apply(p)).

type BorderMode

type BorderMode int

BorderMode selects how samples that fall outside the input image are filled.

const (
	// BorderConstant fills out-of-bounds samples with a constant value
	// (scikit-image mode "constant" with cval).
	BorderConstant BorderMode = iota
	// BorderEdge clamps out-of-bounds coordinates to the nearest edge pixel
	// (scikit-image mode "edge").
	BorderEdge
)

type Format

type Format int

Format identifies an encodable image format.

const (
	// PNG is the lossless PNG format.
	PNG Format = iota
	// JPEG is the lossy JPEG format, encoded at the package's default quality.
	JPEG
)

type Interp

type Interp int

Interp selects the interpolation order used when sampling between pixels.

const (
	// InterpNearest samples the single nearest pixel (scikit-image order 0).
	InterpNearest Interp = iota
	// InterpBilinear blends the four surrounding pixels (scikit-image order 1).
	InterpBilinear
)

type Kernel

type Kernel struct {
	Width   int
	Height  int
	Weights []float64
}

Kernel is a 2-D convolution kernel: Weights is a row-major slice of length Width*Height, and both Width and Height must be odd.

type Orientation

type Orientation uint8

Orientation is the EXIF image-orientation tag (TIFF tag 0x0112). Its eight legal values, 1 through 8, each describe how the stored pixels must be transformed to be displayed the right way up. ExifTranspose applies the matching transform. OrientationNormal (1) is the identity.

const (
	// OrientationNormal (1) needs no transform.
	OrientationNormal Orientation = 1
	// OrientationFlipHorizontal (2) is mirrored left-to-right.
	OrientationFlipHorizontal Orientation = 2
	// OrientationRotate180 (3) is rotated 180 degrees.
	OrientationRotate180 Orientation = 3
	// OrientationFlipVertical (4) is mirrored top-to-bottom.
	OrientationFlipVertical Orientation = 4
	// OrientationTranspose (5) is reflected across the main diagonal.
	OrientationTranspose Orientation = 5
	// OrientationRotate90CW (6) needs a 90-degree clockwise rotation.
	OrientationRotate90CW Orientation = 6
	// OrientationTransverse (7) is reflected across the anti-diagonal.
	OrientationTransverse Orientation = 7
	// OrientationRotate90CCW (8) needs a 90-degree counter-clockwise rotation.
	OrientationRotate90CCW Orientation = 8
)

The eight EXIF orientation values, named for the transform each one calls for. The comment on each is the geometric operation ExifTranspose applies.

func OrientationFromExif

func OrientationFromExif(data []byte) Orientation

OrientationFromExif extracts the EXIF orientation tag from an encoded image's bytes (a JPEG APP1/Exif segment, or a bare TIFF stream). It is deliberately lenient: when no EXIF block, no orientation tag, or a malformed structure is found, it returns OrientationNormal so callers can auto-orient unconditionally. A value stored outside the legal 1-8 range is also normalised to OrientationNormal.

type ResizeMode

type ResizeMode int

ResizeMode selects the interpolation used by Resize.

const (
	// NearestNeighbor selects the source pixel nearest to each destination
	// pixel. It is fast and exact for integer scale factors but blocky.
	NearestNeighbor ResizeMode = iota
	// Bilinear linearly interpolates the four nearest source pixels. It is
	// smoother than nearest-neighbour at the cost of more arithmetic.
	Bilinear
	// Area averages the source region each destination pixel covers, weighting
	// every source pixel by how much of it falls inside. It is the mode to
	// REDUCE with: nearest-neighbour discards all but one source pixel per
	// destination pixel, so shrinking by four throws away fifteen sixteenths of
	// the image and aliases what is left, while bilinear only ever looks at
	// four neighbours however far the image is shrunk. Enlarging, where each
	// destination pixel falls inside one source pixel, it reduces to
	// nearest-neighbour.
	//
	// This is PIL's Image.BOX and OpenCV's INTER_AREA; at integer ratios it
	// reduces to scikit-image's downscale_local_mean.
	Area
	// Bicubic resamples with the Keys cubic (a = -1/2, the Catmull-Rom spline),
	// a smooth four-tap kernel whose footprint widens with the reduction factor:
	// sharper than Bilinear when enlarging and a proper antialiasing low-pass
	// when reducing. It is Pillow's BICUBIC / x/image/draw's CatmullRom. The
	// colour channels are filtered in premultiplied-alpha space so a transparent
	// pixel's colour does not bleed into the visible edge of a cut-out; on a
	// fully opaque image that is exactly the plain cubic. Higher quality than the
	// three modes above, at more arithmetic.
	Bicubic
	// Lanczos resamples with the a = 3 windowed-sinc kernel: wider and sharper
	// than Bicubic at more cost, the highest-fidelity mode in either direction.
	// It is Pillow's LANCZOS / x/image/draw's Lanczos, and like Bicubic filters
	// the colour channels in premultiplied-alpha space.
	Lanczos
)

Directories

Path Synopsis
internal
kernels
Package kernels holds the per-pixel inner loops used by the public image operations.
Package kernels holds the per-pixel inner loops used by the public image operations.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL