en-US/about_PSImageConverter.help.txt

TOPIC
    about_PSImageConverter

SHORT DESCRIPTION
    Cross-platform image conversion (HEIC/HEIF and more), powered by Magick.NET.

LONG DESCRIPTION
    PSImageConverter converts images, for example HEIC/HEIF photos taken with
    an iPhone, to JPEG, PNG, WebP, AVIF, HEIC, JPEG XL, TIFF, BMP or GIF and
    reads their metadata. The Magick.NET library and its native ImageMagick
    build are bundled with the module, so no codecs need to be installed; only
    HEIC output uses the HEVC encoder of the operating system (see
    WRITING HEIC). It requires PowerShell 7.4 or later and runs on Windows,
    Linux and macOS (x64 and arm64; on Alpine/musl Linux x64 only).

    Cmdlets:

        Convert-Image Converts every format Magick.NET can read to the
                          -Format Jpeg, Png, WebP, Avif, Heic, Jxl (JPEG XL),
                          Tiff, Bmp or Gif.
        ConvertTo-Jpeg Convert-Image -Format Jpeg.
        ConvertTo-Png Convert-Image -Format Png (lossless, no -Quality).
        ConvertTo-WebP Convert-Image -Format WebP.
        ConvertTo-Avif Convert-Image -Format Avif.
        ConvertTo-Heic Convert-Image -Format Heic (needs the HEVC encoder
                          of the operating system, see WRITING HEIC).
        ConvertTo-JpegXL Convert-Image -Format Jxl.
        ConvertFrom-Heic Converts .heic/.heif/.hif files only; -Format
                          defaults to Jpeg.
        Get-ImageInfo Returns the format, dimensions, orientation, camera
                          and date taken of images; its objects can be piped
                          into the conversion cmdlets.

    The conversion cmdlets take -Path (wildcards) or -LiteralPath (aliases
    PSPath and LP; files piped from Get-ChildItem and the objects of
    Get-ImageInfo bind here; folders are skipped), -DestinationPath, -Quality
    (1 to 100, default 90, for Jpeg, WebP, Avif, Heic and Jxl),
    -StripMetadata, -Force, -WhatIf and -Confirm, and return a FileInfo object
    for every written file, the same object Get-Item returns, so it can be
    piped into the next cmdlet. The output gets the base name of the source
    and the extension of the format (.jpg, .png, .webp, .avif, .heic, .jxl,
    .tif, .bmp, .gif). A file given more than once in a call (e.g. by the
    patterns *.heic and IMG_*) is converted or described once. A conversion
    never overwrites its source file or a file written earlier in the same
    call, also not when the folder is reached through another path (a
    junction, a symbolic link, a subst or mapped drive, a UNC path), and never
    leaves a truncated or temporary file behind, also not when it is stopped
    with Ctrl+C.

    Wildcards skip hidden files and folders (on Windows those with the Hidden
    attribute, on Linux and macOS names that start with a dot), as
    Get-ChildItem does without -Force; -LiteralPath and a folder name without
    wildcards reach them.

METADATA
    The EXIF orientation is applied to the pixels, so the output is upright.
    Without -StripMetadata the output keeps the metadata its format can carry:

        Jpeg, Png EXIF, XMP, IPTC, ICC colour profile, comments
        WebP, Avif EXIF, XMP, ICC colour profile
        Heic Windows, Linux: EXIF, XMP, ICC colour profile;
                          macOS: ICC colour profile (sRGB as nclx colour
                          information), no EXIF or XMP (see WRITING HEIC)
        Jxl EXIF, XMP; ICC colour profile only at -Quality 100
        Tiff XMP, IPTC, ICC colour profile, comments
        Gif IPTC, ICC colour profile (browsers ignore it in
                          GIF files), comments
        Bmp ICC colour profile

    Tiff, Gif and Bmp output (and Heic output on macOS) gets no EXIF data
    (date taken, camera, GPS location); a warning is written for every source
    with EXIF data unless -StripMetadata is used.
    -StripMetadata removes EXIF (including the GPS location), XMP, IPTC and
    comments but keeps the ICC colour profile.

    The colours are kept when the source carries an ICC profile, as iPhone
    photos do. A HEIC or AVIF source that signals its colours only through
    nclx/CICP (no ICC profile) with primaries other than sRGB/BT.709 (e.g.
    Display P3 or BT.2020, also HDR with PQ or HLG) is converted without
    colour conversion, so its colours look less saturated; Avif output keeps
    the CICP values.

    Lossy Jxl output (-Quality below 100) is always sRGB, because the bundled
    JPEG XL encoder stores an ICC colour profile only in lossless files. The
    pixels of a source with an ICC profile (e.g. Display P3 or Adobe RGB) are
    converted to sRGB first, so the photo looks the same; colours outside sRGB
    are clipped. Use -Quality 100 or Avif to keep a wide gamut. A grey profile
    in a colour image, which viewers ignore, is ignored here as well. Chrome
    shows a lossless Jxl file whose profile describes its tone curve as a
    table (e.g. Photoshop's 'Dot Gain 20%' grey profiles) without colour
    management; Firefox applies such a profile.

    Bytes after the size the header of an ICC profile declares are not part
    of the profile (Samsung Galaxy HEIC photos, for example, have two zero
    bytes there, and so do JPEGs converted from them). The profile is written
    without them in every output format, with unchanged colours (-Verbose
    says so). The Png encoder refuses a profile with such bytes.

    An ICC profile that the colour engine cannot apply, because it is damaged
    (e.g. cut short inside its tag data) or names an unknown colour space, is
    left out of every output (-Verbose says so): viewers ignore it in the
    source, Png and lossless Jxl output could not be written with it, and
    Chrome and Edge would not display an Avif that carries it. A profile that
    the colour engine can apply but the Png or lossless Jxl encoder cannot
    write (e.g. a version 4 profile whose length is not a multiple of 4, a
    header that declares a few more bytes than the profile has although all
    its tags are complete, or, for Jxl, a rendering intent above 3) is left
    out of that output only, so the file is still written (-Verbose says so).

    Grey images with a grey ICC profile (e.g. Photoshop's 'Dot Gain 20%' or
    'Gray Gamma 1.8', scanned greyscale) keep their grey pixels and the
    profile in Jpeg, Png, Tiff and lossless Jxl output. WebP, Avif, Heic, Bmp
    and Gif store grey pixels as RGB, for which a grey profile is not valid,
    so the pixels are converted to sRGB first. This includes a grey JPEG XL
    with a grey profile, which ImageMagick reads as RGB.

    A profile made for other pixels is not valid: viewers ignore a grey
    profile in a colour image, and browsers apply an RGB profile to the pixels
    of a grey JPEG, while Windows (Photos, Explorer) rejects it. Such a
    profile is kept, except where the file could not be written or would look
    different with it: it is left out of lossless Jxl output and, as a grey
    profile on colour pixels, out of Avif, Heic and Bmp output, and out of
    Jpeg, Png and Tiff output when the colour pixels are all neutral grey
    (these formats store them as grey). Neutral grey pixels with an RGB
    profile (a black-and-white photo in Adobe RGB) are stored as RGB in Jpeg,
    Png and Tiff output, so the profile stays valid.

    When the orientation is applied, an EXIF thumbnail is removed (it would
    show rotated against the photo), and the XMP orientation and pixel
    dimensions are updated.

    CMYK images (e.g. print JPEGs) stay CMYK with their colour profile in Jpeg
    and Tiff output; for the other formats they are converted to sRGB through
    their CMYK profile.

    Jpeg has no transparency: transparent and half-transparent pixels are
    placed on a white background (paper white for a CMYK image). So does Heic
    output on Windows, whose encoder takes opaque pixels only. The other
    formats keep the alpha channel (Gif only fully transparent or opaque
    pixels).

FRAMES AND DAMAGED FILES
    Only the first frame of an animated GIF or WebP, a multi-page TIFF or a
    HEIC file with several images is converted. A warning is written for every
    such source: 'Only the first of <N> frames is converted: <path>'.

    A source that ImageMagick can read but reports as damaged, e.g. a JPEG cut
    off by an interrupted copy (the missing part becomes grey) or an animated
    GIF cut off after its first frame (its frames cannot be counted then), is
    converted with the warning 'The image may be damaged (<reasons>): <path>';
    Get-ImageInfo writes the same warning. A JPEG that lacks only its end
    marker gets this warning as well, although its pixels are complete: the
    decoder reports it like a file that is cut off. Other ImageMagick
    warnings about a source go to the verbose stream.

WRITING HEIC
    HEVC, the codec of HEIC, is covered by patent pools, and shipping an HEVC
    encoder would pass their licensing duties on to every user, so the module
    ships none. ConvertTo-Heic and -Format Heic use the encoder of the
    operating system:

        Windows 10 1809+ the WIC HEIF encoder; install the 'HEVC Video
                          Extensions' from the Microsoft Store (and the
                          'HEIF Image Extension' if it is missing)
        macOS ImageIO; nothing to install, but the Mac needs an
                          HEVC encoder
        Linux libheif 1.12 or later with its HEVC encoder x265
                          (built in or as a plugin):
                          Ubuntu 24.04 and 26.04, Debian 13:
                            sudo apt install libheif1 libheif-plugin-x265
                          Ubuntu 22.04, Debian 12: sudo apt install libheif1
                          Fedora: enable RPM Fusion free
                            (https://rpmfusion.org), then
                            sudo dnf install libheif-freeworld
                          Arch: sudo pacman -S libheif
                          Alpine: apk add libheif libheif-x265

    The first HEIC conversion in a session checks with a small test image that
    the encoder works; after installing one, start a new session. When it is
    missing, the call stops before any file is read, also with -WhatIf, with
    one terminating error HeicEncoderNotAvailable that says what to install.
    -Format Avif stores AV1 in the same HEIF container and needs nothing
    installed.

    Windows and Linux keep EXIF and XMP as they are (the EXIF orientation is
    set to 1) and the ICC colour profile; macOS writes no EXIF or XMP and
    keeps the ICC colour profile, except sRGB, which ImageIO stores as nclx
    colour information instead. Windows stores no alpha channel (transparent
    pixels are placed on white, as for Jpeg); Linux and macOS keep it. An
    image the encoder rejects fails with ImageWriteFailed: on Windows a side
    longer than 22528 pixels; on Linux a side longer than 32768 pixels, one
    shorter than 32 pixels next to one longer than 4096, or an image whose
    slices stay larger than Magick.NET reads even in 13 slices; which sizes
    ImageIO rejects on macOS is not checked yet.

    On Linux an image is encoded as one HEVC slice. Magick.NET cannot read a
    slice larger than 16 MiB, so only when that slice is larger than
    15,000,000 bytes (a photo with much detail from about 25 megapixels,
    random noise from about 10) is the image encoded again in up to 13 slices.
    libheif 1.17 and older store such slices as several extents (parts of the
    file), which Windows' own viewer refuses; the module joins them into one.

    Windows' own viewer (Photos, Explorer) does not show a HEIC image with a
    side shorter than 8 pixels, whichever program wrote it, and ignores the
    alpha channel of a HEIC file. It does not open HEIC files written on Linux
    with a side longer than 16384 pixels. At -Quality 90 HEIC files are larger
    than JPEG files at 90 (about 1.1 to 2 times on Windows, 2.4 to 3.4 times
    with x265 on Linux); -Quality 60 to 80 gives about the size of JPEG at 90.
    Like AVIF and WebP, HEIC stores colour at half resolution (4:2:0), so thin
    coloured lines blur slightly.

    In a container that denies the NUMA memory-policy calls (Docker's default
    seccomp profile), x265 writes harmless lines to the console that
    PowerShell cannot redirect: with x265 3.5 about 50 lines
    'set_mempolicy: Operation not permitted' per image up to 8 megapixels
    (and once more for the encoder check of a session) and about one per
    processor for a larger one, with x265 4.1
    about one line 'unable to set thread affinity for NUMA node mask' per
    processor for an image larger than 8 megapixels or of several slices,
    which gets an explicit thread pool. x265 4.1 makes no pool of its own
    there, so it encodes smaller images 3 to 5 times more slowly. Both go
    away with docker run --cap-add SYS_NICE.
    On Arch Linux, libheif writes 'dlopen: ... cannot open shared object file'
    lines for plugins whose optional libraries are not installed; they are
    harmless too.

ERRORS
    Errors about a file are non-terminating: the cmdlets continue with the
    next file, $? is $false afterwards, -ErrorVariable holds one record per
    failed file and -ErrorAction Stop stops at the first one. Every error has
    a stable id:

        PathNotFound the input matches no file; for a pattern the
                                message names a drive or folder that does not
                                exist, a folder part that is a file, a folder
                                part with wildcards that no folder (or only a
                                hidden one) matches, or, when the folder
                                exists, adds that wildcards skip hidden files
                                and that [ and ] are wildcard characters and
                                how to match them; for a PSDrive that is not
                                global it names the drive (see PSDRIVES)
        InvalidWildcardPattern -Path is not a valid pattern ('a[.jpg'); use
                                -LiteralPath
        UnsupportedProvider the input is not a file system path (one error
                                per input, also for a pattern)
        NotHeicFile ConvertFrom-Heic: no .heic/.heif/.hif file
        TargetIsSource the output would overwrite the source
        DuplicateTarget two sources of one call map to one output
        TargetExists the output exists and -Force is not set
        ConfirmationFailed -Confirm (or the caller's $ConfirmPreference)
                                asks for a confirmation the host cannot show,
                                e.g. in pwsh -NonInteractive; use
                                -Confirm:$false
        ImageReadFailed the source cannot be read
        ImageWriteFailed the output cannot be written (also when
                                the HEIC encoder rejects the image), or the
                                destination folder cannot be created or
                                written to

    The target object is the source file; for PathNotFound,
    InvalidWildcardPattern and UnsupportedProvider it is the input as given.

    A -DestinationPath that cannot name a folder is a parameter error: it
    stops the whole call with one terminating error before any file is read,
    also with -WhatIf, and try/catch catches it without -ErrorAction Stop. Use
    $_ in the catch block or $Error[0] for the error record: -ErrorVariable
    holds the exception for these errors (its ErrorRecord property has the
    record). The ids are InvalidDestinationPath (white space only, an unknown
    drive or provider, invalid characters, on Windows also a colon after the
    drive such as C:\x:y, a drive-qualified path that climbs above the root of
    its drive such as Temp:\..\x, a PSDrive that is not global, see PSDRIVES),
    UnsupportedProvider (not a file system path) and DestinationNotDirectory
    (the path or one of its parent folders is a file, a junction or symbolic
    link whose target does not exist, or a link that cannot be resolved, such
    as one that points to itself); the target object is the -DestinationPath
    value. -Format Heic on a computer without a working HEVC encoder stops the
    call the same way with HeicEncoderNotAvailable (target object Heic). A
    relative path that climbs above the root of its drive stays at the root,
    as in other cmdlets. A folder that does not exist yet is created when the
    first file is written; when that fails (no permission, a read-only file
    system) or the folder cannot be written to, each file gets
    ImageWriteFailed.

    ConvertFrom-Heic and the ConvertTo-* cmdlets pass all files of one call to
    a single Convert-Image, so the errors it finds have the
    FullyQualifiedErrorId '<Id>,Convert-Image'. The checks they make before
    carry their own name: the -DestinationPath errors of every one of them,
    HeicEncoderNotAvailable of ConvertTo-Heic and ConvertFrom-Heic, and the
    input errors (PathNotFound, InvalidWildcardPattern, UnsupportedProvider,
    NotHeicFile) of ConvertFrom-Heic. With Import-Module -Prefix, the errors
    raised inside Convert-Image during such a call keep the module's
    unprefixed name ',Convert-Image', while a direct call of the prefixed
    cmdlet and the checks of a prefixed wrapper carry the prefixed name.

    Separate calls or processes that write the same output file at the same
    time without -Force are not coordinated: on Windows the later one fails
    with TargetExists or ImageWriteFailed; on Linux and macOS both can succeed
    and the later file replaces the earlier one. With -Force the later file
    replaces the earlier one on every platform. Give parallel conversions
    different destination folders.

PREFERENCE VARIABLES
    Like compiled cmdlets, the cmdlets follow the preference variables of
    their caller ($WhatIfPreference, $ConfirmPreference,
    $ErrorActionPreference, $WarningPreference, $InformationPreference,
    $VerbosePreference, $DebugPreference), also when a script or function
    sets them in its own scope. Functions of a script module do not see such
    values by themselves (PowerShell issue #4568), so each cmdlet copies them
    when it starts. A function with SupportsShouldProcess that is called with
    -WhatIf therefore passes it on, and $ErrorActionPreference = 'Stop' in a
    script makes the first error terminating. A common parameter given to the
    cmdlet itself (-WhatIf, -Confirm, -ErrorAction, -WarningAction,
    -InformationAction, -Verbose, -Debug) wins.

PSDRIVES
    PSDrives that are not global cannot be used. A script module resolves
    paths in its own session state, which sees only the global drives; this
    is a limitation of the PowerShell engine (Microsoft.PowerShell.Archive
    behaves the same). A drive created with New-PSDrive without -Scope Global
    inside a function (also by a script dot-sourced there), at the top of a
    script run by its path or with & (.\setup.ps1, & .\setup.ps1), or in
    another module therefore fails in -Path and -LiteralPath with
    PathNotFound and in -DestinationPath with InvalidDestinationPath, and so
    does a relative input path while the current location is on such a
    drive. The message names the drive and its root. Create the drive with
    New-PSDrive -Scope Global, or use the file system path. A drive created
    at the prompt, in the profile, at the top level of pwsh -File or by a
    script dot-sourced there (. .\setup.ps1) is global and works.

    When such a drive has the name of a global drive (e.g. a script's own
    Temp:), the module would read and write on the global drive instead, so
    it fails the same way and the message names both roots. For a drive of
    another provider the message says that only file system paths are
    supported. Files piped from Get-ChildItem or Get-Item on such a drive
    work, because their PSPath holds the file system path, and so does a
    relative -DestinationPath; strings and the output of Resolve-Path (which
    has no PSPath) do not.

EXAMPLES
    PS C:\> ConvertFrom-Heic -Path .\IMG_1234.HEIC

    PS C:\> Get-ChildItem -Path C:\Pictures -Filter *.heic | ConvertFrom-Heic -Format Png

    PS C:\> Get-ChildItem -Path C:\Pictures -Filter *.jpg | ConvertTo-WebP -Quality 80 -DestinationPath C:\Pictures\web

    PS C:\> ConvertTo-Avif -Path .\IMG_1234.HEIC -StripMetadata

    PS C:\> ConvertTo-Heic -Path .\export\*.png -Quality 80

    PS C:\> Convert-Image -Path C:\Pictures\*.png -Format Tiff -WhatIf

    PS C:\> Get-ChildItem -Path C:\Pictures -Filter *.heic | Get-ImageInfo

    PS C:\> Get-ImageInfo -Path .\*.heic | Where-Object -Property Width -GT 4000 | ConvertTo-Jpeg

NOTE:
    Thank you to all those who contributed to this module, by writing code,
    sharing opinions, and provided feedback.

TROUBLESHOOTING NOTE:
    Look out on the GitHub repository for issues and new releases:
    https://github.com/fasteiner/PSImageConverter

SEE ALSO
    - Get-Help Convert-Image -Full
    - https://github.com/dlemstra/Magick.NET

KEYWORDS
    HEIC, HEIF, HEVC, Image, Conversion, JPEG, PNG, WebP, AVIF, JpegXL,
    ImageMagick