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
<PackageReference Include="JRK.JevRunner.Image" Version="0.6.0" />
<PackageVersion Include="JRK.JevRunner.Image" Version="0.6.0" />
<PackageReference Include="JRK.JevRunner.Image" />
paket add JRK.JevRunner.Image --version 0.6.0
#r "nuget: JRK.JevRunner.Image, 0.6.0"
#:package JRK.JevRunner.Image@0.6.0
#addin nuget:?package=JRK.JevRunner.Image&version=0.6.0
#tool nuget:?package=JRK.JevRunner.Image&version=0.6.0
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:
ValidImageResultexposes non-nullFormat(pngorjpg),Width,Height,FileSizeBytes, and the original encodedBytesread during validation.InvalidImageResultexposesErrorsandSuggestedScaling, not image metadata or bytes.- The union retains
IsValidandThrowIfInvalid()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 | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net11.0 is compatible. |
-
net11.0
- JRK.JevRunner (>= 0.6.0)
- SkiaSharp (>= 4.153.1)
- SkiaSharp.NativeAssets.Linux.NoDependencies (>= 4.153.1)
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.