JRK.JevRunner.Image 0.6.0

dotnet add package JRK.JevRunner.Image --version 0.6.0
                    
NuGet\Install-Package JRK.JevRunner.Image -Version 0.6.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="JRK.JevRunner.Image" Version="0.6.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="JRK.JevRunner.Image" Version="0.6.0" />
                    
Directory.Packages.props
<PackageReference Include="JRK.JevRunner.Image" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add JRK.JevRunner.Image --version 0.6.0
                    
#r "nuget: JRK.JevRunner.Image, 0.6.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package JRK.JevRunner.Image@0.6.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=JRK.JevRunner.Image&version=0.6.0
                    
Install as a Cake Addin
#tool nuget:?package=JRK.JevRunner.Image&version=0.6.0
                    
Install as a Cake Tool

JRK.JevRunner.Image

Optional SkiaSharp helpers for images used with Jev. The core client does not depend on this project.

Targets .NET 11 and uses preview C# unions for image validation results, errors, and AppliedBox.

Validate an image before uploading

using JRK.JevRunner.Image;
using TruePath;

var path = AbsolutePath.Create(@"C:\images\photo.jpg");
var validator = new ImageValidator();
var validation = validator.Validate(path);
switch (validation)
{
    case ValidImageResult valid:
        Console.WriteLine($"{valid.Format}: {valid.Width} × {valid.Height}, {valid.FileSizeBytes} bytes");
        byte[] originalBytes = valid.Bytes;
        break;
    case InvalidImageResult invalid:
        Console.WriteLine($"Suggested scaling: {invalid.SuggestedScaling}");
        foreach (var error in invalid.Errors)
            Console.WriteLine(error.GetErrorMessage());
        break;
}
validation.ThrowIfInvalid(); // Throws InvalidOperationException containing the errors if invalid.

ImageValidator checks actual contents, not the filename: PNG or JPEG only, 280–1568 pixels on each axis (inclusive), at most 16 MiB of encoded file data, no animated PNG (including single-frame APNG), and successful pixel decoding. Non-default EXIF orientation is rejected: rotate the pixels upright and reset the orientation before uploading. Visually sideways pixels without orientation metadata cannot be detected. Progressive JPEG is not rejected; baseline JPEG is recommended.

Validation does not modify, rotate, or resize the file. Supply an IFileSystem to the constructor for injectable file access. Invalid contents return errors; missing or inaccessible files throw normal file-access exceptions. Oversized files are rejected before decoding.

ImageValidationResult is a union of ValidImageResult and InvalidImageResult. Pattern-match before accessing case-specific properties:

  • ValidImageResult exposes non-null Format (png or jpg), Width, Height, FileSizeBytes, and the original encoded Bytes read during validation.
  • InvalidImageResult exposes Errors and SuggestedScaling, not image metadata or bytes.
  • The union retains IsValid and ThrowIfInvalid() for case-independent checks.

Migration: replace the former result constructor with the appropriate case. Move metadata access into the valid branch, and error/scaling access into the invalid branch. Valid results no longer expose an empty error list or a scaling suggestion.

This is a per-file check, not a guarantee of API acceptance. It cannot check the complete request's 32 MiB body, processed-image and expanded-reference limits, asset references, base64 representation, or model context length. AddImageAsset does not run validation automatically and still uses the filename extension as the upload format: use a matching lowercase .png, .jpg, or .jpeg extension. See the current API requirements.

InvalidImageResult.Errors contains ImageValidationError unions: a typed ImageDimensionError with Dimension, Violation, Limit, and Actual, NotASupportedImageError, ImageSizeExceededError, or a string for other failures. Use GetErrorMessage() to obtain a message from any case. ImageDimensionError.GetErrors(info, minimum, maximum) can also check dimensions independently, returning width violations before height violations.

InvalidImageResult.SuggestedScaling belongs to that result, not the validator. It reports Upscale when dimension errors are only ToSmall, Downscale when they are only ToHigh, and ScalingNotPossible when both occur. None means no dimension errors, not necessarily a valid image. Non-dimension errors do not affect the suggestion, and aspect-ratio feasibility is not checked. Always inspect IsValid and revalidate resized output: out-of-range images have not undergone full pixel decoding, and scaling can leave the other side outside the allowed range.

Upscale or downscale an image

var scaler = new ImageScaler();
byte[] enlargedPng = scaler.Upscale(path);     // Shorter side becomes 280 if smaller.
byte[] reducedPng = scaler.Downscale(path);    // Longer side becomes 1568 if larger.
// Optional custom bounds:
byte[] customPng = scaler.Upscale(path, minimumDimension: 400);

Both axes use the same proportional scale factor. Upscaling rounds fractional dimensions up; downscaling rounds down, keeping at least one pixel. Images already meeting the chosen bound keep their dimensions. Results are always PNG bytes, including when no resizing is needed, and the source file is never changed.

The operations are independent: they do not crop, pad, normalize EXIF orientation, or validate the result. An extreme aspect ratio may still violate API limits, and that does not throw: 200 × 1600 upscales to 280 × 2240; 2000 × 200 downscales to 1568 × 156. Revalidate the saved output if API readiness is required; save it with a .png extension. Decode, file-access, resource-allocation, and invalid-target errors can still fail the operation. Supply an IFileSystem to the constructor for injectable file access.

Add an asset from resized bytes

using JRK.JevRunner.Infrastructure;
using JRK.JevRunner.Requests;

byte[] png = new ImageScaler().Downscale(path);
var asset = ImageAsset.FromByteArray(Guid.NewGuid().ToString(), "png", png);
var state = new AssetState().AddAsset("photo", asset);

FromByteArray encodes bytes as standard base64; it does not detect the format, convert pixels, or validate them. The supplied format must match the actual bytes. Populate AssetState before constructing the request or executing a generated query.

Validate and add an image asset

using JRK.JevRunner.Image;
using JRK.JevRunner.Requests;
using TruePath;

var state = new AssetState();
await state.AddImageWithValidation(
    "photo", Guid.NewGuid().ToString(),
    AbsolutePath.Create(@"C:\images\photo.jpg"));

AddImageWithValidation validates before registering the asset and throws InvalidOperationException containing the validation errors if the image is invalid. It does not resize or modify the image: valid files retain their original bytes and detected PNG or JPEG format, regardless of the filename extension. stateName names the state field; assetName identifies the image in the request's assets map.

The method returns the same state instance, preserving its derived type. Optional token and fileSystem arguments support cancellation and injectable file access; validation and byte loading use the same file system. File-access failures propagate without registering an asset. Populate the state before constructing a request or executing a generated query.

Use this helper when invalid images should be rejected rather than resized. For a best-effort resize, use the automatic adjustment helper below; neither helper checks request-wide API limits.

Automatic image adjustment (experimental)

var state = new AssetState();
await state.AddImageAssetWithAutoAdjustment(
    "photo", Guid.NewGuid().ToString(),
    AbsolutePath.Create(@"C:\images\photo.png"));

The helper validates the file, attempts one Upscale or Downscale when suggested, and registers the resulting bytes. Unchanged images retain their detected format and original bytes, while resized images are encoded as PNG. Validation, byte loading, and scaling all use the supplied IFileSystem (or the local file system by default). If validation fails with None or ScalingNotPossible, it throws InvalidOperationException containing the validation errors instead. Source files are unchanged.

Current limitations: adjusted output is not revalidated, and suggestions ignore non-dimension errors. A resize can leave the other axis outside the limits, so this helper is not a guarantee of API acceptance. For strict validation, revalidate the prepared bytes before registering them explicitly.

Draw a Box answer

using JRK.JevRunner.Image;
using TruePath;

var box = response.GetRequiredBoxAnswer("Find_Head");
var renderer = new BoxRenderer();
byte[] png = renderer.DrawBox(
    AbsolutePath.Create(@"C:\images\photo.jpg"), box);

DrawBox returns PNG bytes without saving or overwriting any file. It preserves the image dimensions and draws a red, three-pixel outline by default. Supply color: SKColors.Blue and/or strokeWidth: 5 to customize it (with using SkiaSharp;). The stroke is centered on the box edges and clipped at image boundaries.

Draw multiple Box answers

DrawBox takes an AppliedBox union, accepting either a single BoxAnswer or an IEnumerable<BoxAnswer>. All boxes use the same color and stroke width:

using JRK.JevRunner.Responses;
using SkiaSharp;

var boxes = new List<BoxAnswer>
{
    response.GetRequiredBoxAnswer("Head"),
    response.GetRequiredBoxAnswer("Glasses")
};
byte[] png = renderer.DrawBox(
    AbsolutePath.Create(@"C:\images\photo.jpg"), boxes,
    color: SKColors.Green, strokeWidth: 4);

Generated query response properties also return BoxAnswer, so they can be passed directly or collected into the list. Supply a repeatable collection such as an array or list: rendering enumerates the boxes once for validation and again for drawing. An empty collection returns the source image re-encoded as PNG without outlines.

Coordinates and file access

Use the same image pixel layout you submitted to Jev: this method does not resize the source or apply EXIF orientation. Coordinates must be finite fractions between zero and one, with Top < Bottom and Left < Right. Confidence does not gate rendering; check target presence and confidence in your application first.

Pass an IFileSystem to the renderer constructor for injectable file access. Native SkiaSharp assets for Windows and macOS arrive with SkiaSharp; this project also references SkiaSharp.NativeAssets.Linux.NoDependencies for supported Linux targets.

Product Compatible and additional computed target framework versions.
.NET net11.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.6.0 91 10/26/2026
0.6.0-preview-01 89 10/25/2026
0.5.0 86 10/19/2026
0.5.0-preview-02 83 10/19/2026
0.5.0-preview-01 84 10/18/2026

Image validation results
- Breaking: ImageValidationResult is now a union of ValidImageResult and InvalidImageResult rather than a record with shared metadata and errors.
- Pattern-match valid results for detected Format, Width, Height, FileSizeBytes, and original encoded Bytes. Invalid results expose Errors and SuggestedScaling.
- IsValid and ThrowIfInvalid() remain available on the union.
- Replaced string errors with NotASupportedImageError for non-image, undecodable, or unsupported contents and ImageSizeExceededError exposing the maximum encoded file size. Both support GetErrorMessage().
- Fixed undersized width and height validation messages to report that the dimension is too small.

Image asset integration
- Image helpers now construct ImageAsset with bytes through the FileData API instead of the obsolete FromByteArray factory.
- Validation and automatic adjustment helpers handle the valid and invalid result cases. Resized output remains PNG and is not revalidated.