No description
Find a file
2026-08-21 18:15:38 +02:00
.claude Restructure into version-neutral core + V4 tree for upcoming v5 support 2026-07-07 12:19:38 +02:00
.github Add NuGet release workflow and package URLs 2026-01-31 16:03:43 +01:00
samples/NovelAI.ImageGen.Sample Restructure into version-neutral core + V4 tree for upcoming v5 support 2026-07-07 12:19:38 +02:00
src/NovelAI.ImageGen Review feedback (Jibril, PR #4): restore the never-throws contract and the mask's impossible-reference guard 2026-08-21 15:52:37 +00:00
tests/NovelAI.ImageGen.Tests Review feedback (Jibril, PR #4 round 3): test the InvalidImageContentException arm 2026-08-21 16:04:21 +00:00
.gitignore Initial project setup 2026-01-31 13:28:22 +01:00
CHANGELOG.md fix(v5): grid-align inpainting masks before upload — the 8x8 latent-cell requirement the V4 path always honored 2026-08-21 14:58:13 +00:00
CODEOWNERS chore: point package URLs + CODEOWNERS to Forgejo/TeamAI 2026-06-27 13:47:35 +02:00
Directory.Build.props Implement NovelAI.ImageGen library for image generation 2026-01-31 16:03:43 +01:00
Directory.Packages.props Add sample console application for testing 2026-01-31 16:03:43 +01:00
LICENSE Implement NovelAI.ImageGen library for image generation 2026-01-31 16:03:43 +01:00
NovelAI.ImageGen.slnx Add sample console application for testing 2026-01-31 16:03:43 +01:00
README.md docs: Diffusion 5 changelog entry + lean README V5 section 2026-08-21 04:08:18 +00:00

NovelAI.ImageGen

A .NET library for NovelAI image generation with ASP.NET Core dependency injection support. Focuses on the Anime Diffusion 4.5 model with support for inpainting, vibe transfer, and character references.

License: MIT .NET

Features

  • Strongly-typed request/response models - No magic strings, full IntelliSense support
  • Result pattern - Explicit success/failure handling without exceptions
  • ASP.NET Core DI integration - First-class IHttpClientFactory support
  • V4 Prompt System - Multi-character scenes with positioning
  • Tag System - Intuitive tag creation with emphasis weights
  • All Generation Modes:
    • Text-to-image generation
    • Image-to-image (img2img)
    • Inpainting with masks
    • Precise reference (style, character, character+style)
    • Vibe transfer
    • Emotion augmentation

Installation

dotnet add package NovelAI.ImageGen

Or add to your .csproj:

<PackageReference Include="NovelAI.ImageGen" Version="1.0.0" />

Quick Start

Namespaces

The examples below assume the following usings. Generation request types (ImageGenerationRequest, Character, option classes) live in the version-specific Models.V4 namespace, so a future v5 request tree can exist alongside them:

using NovelAI.ImageGen.Contracts;       // INovelAIClient
using NovelAI.ImageGen.Extensions;      // AddNovelAI
using NovelAI.ImageGen.Models;          // Result, GeneratedImage, Tag, shared enums
using NovelAI.ImageGen.Models.Requests; // AugmentEmotionRequest
using NovelAI.ImageGen.Models.V4;       // ImageGenerationRequest, Character, options

1. Register Services

// Program.cs or Startup.cs
services.AddNovelAI(options =>
{
    options.ApiKey = "your-novelai-api-key";
});

2. Inject and Use

public class ImageService
{
    private readonly INovelAIClient _client;

    public ImageService(INovelAIClient client)
    {
        _client = client;
    }

    public async Task<byte[]?> GenerateAsync()
    {
        var result = await _client.GenerateImageAsync(new ImageGenerationRequest
        {
            Width = 1024,
            Height = 1024,
            PositiveTags = ["1girl", "solo", "smile", "blue eyes"],
            NegativeTags = ["lowres", "bad quality"]
        });

        return result switch
        {
            Result<GeneratedImage>.Success s => s.Value.Data,
            Result<GeneratedImage>.Failure f => throw new Exception(f.Error),
            _ => null
        };
    }
}

Usage Examples

Basic Generation

var result = await client.GenerateImageAsync(new ImageGenerationRequest
{
    Width = 1024,
    Height = 1024,
    PositiveTags =
    [
        "1girl",
        "solo",
        "standing",
        "outdoors",
        "sunset"
    ],
    NegativeTags = ["lowres", "bad quality", "blurry"]
});

if (result is Result<GeneratedImage>.Success success)
{
    await File.WriteAllBytesAsync("output.png", success.Value.Data);
    Console.WriteLine($"Generated with seed: {success.Value.Seed}");
}

Using Tag Emphasis

Tags support emphasis weights to strengthen or weaken their influence:

var result = await client.GenerateImageAsync(new ImageGenerationRequest
{
    Width = 1024,
    Height = 1024,
    PositiveTags =
    [
        "1girl",
        "solo",
        Tag.WithStrength("blue eyes", 1.5),      // Stronger emphasis
        Tag.WithStrength("simple background", 0.5), // Weaker emphasis
        "long hair"  // Implicit conversion from string
    ]
});

Multi-Character Scenes (V4 Prompt)

Create scenes with multiple characters, each with their own tags and positioning:

var result = await client.GenerateImageAsync(new ImageGenerationRequest
{
    Width = 1216,
    Height = 832,
    PositiveTags = ["park", "sunny day", "bench"],  // Scene/background tags
    Characters =
    [
        new Character
        {
            Gender = CharacterGender.Girl,
            Position = Position.FromGrid(row: 3, column: 2),  // Left-center
            PositiveTags = ["long blonde hair", "blue eyes", "school uniform"],
            NegativeTags = ["standing"]
        },
        new Character
        {
            Gender = CharacterGender.Boy,
            Position = Position.FromGrid(row: 3, column: 4),  // Right-center
            PositiveTags = ["short black hair", "casual clothes"]
        }
    ]
});

Position Options

// Let NovelAI decide positioning
Position.Auto

// Exact coordinates (0.0-1.0 range)
Position.At(0.3, 0.5)

// 5x5 grid positioning (row 1-5, column 1-5)
Position.FromGrid(row: 2, column: 4)

Diffusion 5 (V5)

Diffusion 5 models use a separate request tree in Models.V5 with free-form positioning (no grid) and alpha transparency. The client sends v5 requests as multipart/form-data; source images and masks are uploaded raw at native resolution:

using NovelAI.ImageGen.Models.V5;

var result = await client.GenerateImageAsync(new ImageGenerationRequest
{
    Width = 1216,
    Height = 832,
    PositiveTags = ["1girl", "street"],
    TransparentBackground = true,
    Characters =
    [
        new Character
        {
            PositiveTags = ["long blonde hair"],
            Position = Position.At(0.32, 0.48)  // free-form, no grid
        }
    ]
});

Image-conditioned modes take CachedImage values. Upload from bytes, or skip the upload entirely with a known server-side cache key:

Img2Img = new Img2ImgOptions
{
    Image = CachedImage.FromData(await File.ReadAllBytesAsync("source.png")),
    Strength = 0.7,
    ColorCorrect = true
}

Vibe transfer and precise reference are not yet available for v5 requests.

Vibe Transfer

Transfer the artistic style from a reference image:

// Step 1: Encode the reference image
var vibeResult = await client.EncodeVibeAsync(
    imageData: await File.ReadAllBytesAsync("style-reference.png"),
    model: Model.Diffusion45Full,
    informationExtracted: 0.7  // How much style to extract (0.0-1.0)
);

if (vibeResult is Result<VibeEmbedding>.Success vibe)
{
    // Step 2: Use the embedding in generation
    var result = await client.GenerateImageAsync(new ImageGenerationRequest
    {
        Width = 1024,
        Height = 1024,
        PositiveTags = ["1girl", "portrait", "fantasy"],
        VibeTransfer = new VibeTransferOptions
        {
            Embedding = vibe.Value,
            Strength = 0.6  // How strongly to apply the style
        }
    });
}

Precise Reference

Use reference images to guide generation with style, character likeness, or both:

var result = await client.GenerateImageAsync(new ImageGenerationRequest
{
    Width = 1024,
    Height = 1024,
    PositiveTags = ["1girl", "standing", "city background"],
    PreciseReference = new PreciseReferenceOptions
    {
        References =
        [
            new PreciseReference
            {
                ImageData = await File.ReadAllBytesAsync("character-ref.png"),
                Type = ReferenceType.Character,  // Character likeness only
                Strength = 0.8,
                Fidelity = 1.0
            }
        ]
    }
});

You can combine multiple references of different types:

PreciseReference = new PreciseReferenceOptions
{
    References =
    [
        new PreciseReference
        {
            ImageData = await File.ReadAllBytesAsync("character.png"),
            Type = ReferenceType.Character,
            Strength = 0.8
        },
        new PreciseReference
        {
            ImageData = await File.ReadAllBytesAsync("style.png"),
            Type = ReferenceType.Style,
            Strength = 0.6
        }
    ]
}

Image-to-Image

Transform an existing image based on your prompt:

var result = await client.GenerateImageAsync(new ImageGenerationRequest
{
    Width = 1024,
    Height = 1024,
    PositiveTags = ["anime style", "vibrant colors"],
    Img2Img = new Img2ImgOptions
    {
        ImageData = await File.ReadAllBytesAsync("source.png"),
        Strength = 0.7,  // How much to deviate from original
        Noise = 0.0
    }
});

Inpainting

Regenerate specific areas of an image using a mask:

var result = await client.GenerateImageAsync(new ImageGenerationRequest
{
    Width = 1024,
    Height = 1024,
    PositiveTags = ["blue hair", "smiling"],  // What to generate in masked area
    Inpaint = new InpaintOptions
    {
        ImageData = await File.ReadAllBytesAsync("image.png"),
        MaskData = await File.ReadAllBytesAsync("mask.png"),  // White = regenerate
        Strength = 1.0
    }
});

Emotion Augmentation

Apply emotions or expressions to characters in an existing image:

var result = await client.AugmentEmotionAsync(new AugmentEmotionRequest
{
    ImageData = await File.ReadAllBytesAsync("character.png"),
    Emotion = Emotion.Happy,
    Defry = 0  // Quality parameter (0 = default)
});

if (result is Result<GeneratedImage>.Success success)
{
    await File.WriteAllBytesAsync("happy-character.png", success.Value.Data);
}

You can also add tags to guide the augmentation:

var result = await client.AugmentEmotionAsync(new AugmentEmotionRequest
{
    ImageData = await File.ReadAllBytesAsync("character.png"),
    Emotion = Emotion.Excited,
    Text = "open mouth, blush"
});

Available Emotions: Neutral, Happy, Sad, Angry, Scared, Surprised, Tired, Excited, Nervous, Thinking, Confused, Shy, Disgusted, Smug, Bored, Laughing, Irritated, Aroused, Embarrassed, Worried, Love, Determined, Hurt, Playful

API Key Validation

var result = await client.ValidateApiKeyAsync();

if (result is Result<bool>.Success)
{
    Console.WriteLine("API key is valid!");
}
else if (result is Result<bool>.Failure f)
{
    Console.WriteLine($"Invalid API key: {f.Error}");
}

Configuration

NovelAIClientOptions

Property Type Default Description
ApiKey string required Your NovelAI API key
ImageApiBaseUrl string https://image.novelai.net/ Image generation API base URL
UserApiBaseUrl string https://api.novelai.net/ User/account API base URL
Timeout TimeSpan 5 minutes Request timeout

ImageGenerationRequest

Property Type Default Description
Width int required Image width (must be divisible by 64)
Height int required Image height (must be divisible by 64)
PositiveTags IReadOnlyList<Tag> required Positive prompt tags
NegativeTags IReadOnlyList<Tag>? null Negative prompt tags
Model Model Diffusion45Full Model to use
Seed long? null (random) Seed for reproducibility
Guidance double 5.0 CFG scale (0.0-10.0)
Sampler Sampler EulerAncestral Sampling algorithm
Steps int 28 Number of sampling steps (1-50)
NoiseSchedule NoiseSchedule Karras Noise schedule
Characters IReadOnlyList<Character>? null Multi-character definitions
PreciseReference PreciseReferenceOptions? null Precise reference settings (style/character/both)
VibeTransfer VibeTransferOptions? null Vibe transfer settings
Img2Img Img2ImgOptions? null Image-to-image settings
Inpaint InpaintOptions? null Inpainting settings

Validation Rules

The library validates requests before sending to the API:

  • Dimensions: Width and height must be divisible by 64
  • Mutual Exclusivity:
    • Inpaint cannot combine with VibeTransfer
    • Img2Img + VibeTransfer is allowed
  • Required Data:
    • Inpaint requires both image and mask data
    • PreciseReference requires at least one reference with image data

Result Pattern

All API methods return Result<T>, a discriminated union for explicit error handling:

var result = await client.GenerateImageAsync(request);

// Pattern matching
var message = result switch
{
    Result<GeneratedImage>.Success s => $"Generated image with seed {s.Value.Seed}",
    Result<GeneratedImage>.Failure f => $"Error: {f.Error} (Status: {f.StatusCode})",
    _ => "Unknown result"
};

// Properties
if (result.IsSuccess)
{
    var image = result.ValueOrDefault;
}

// Match method
result.Match(
    onSuccess: image => SaveImage(image),
    onFailure: (error, statusCode) => LogError(error)
);

// Map to transform success values
Result<int> sizeResult = result.Map(image => image.Data.Length);

Supported Models

Model Identifier Description
Model.Diffusion45Full nai-diffusion-4-5-full Full quality Diffusion 4.5
Model.Diffusion45FullInpainting nai-diffusion-4-5-full-inpainting Inpainting variant

Samplers

Sampler API String
EulerAncestral k_euler_ancestral
Euler k_euler
DpmPlusPlus2SAncestral k_dpmpp_2s_ancestral
DpmPlusPlus2M k_dpmpp_2m
DpmPlusPlusSde k_dpmpp_sde
Ddim ddim

Error Handling

The library provides descriptive error messages for common API errors:

Status Code Description
400 Bad Request - Invalid request parameters
401 Unauthorized - Invalid API key
402 Payment Required - Insufficient Anlas balance
403 Forbidden - Access denied
429 Rate Limited - Too many requests
500+ Server errors

Requirements

  • .NET 10.0 or later
  • Valid NovelAI API key with image generation access

Dependencies

  • Microsoft.Extensions.Http - HttpClient factory
  • Microsoft.Extensions.Options - Options pattern
  • SixLabors.ImageSharp - Image processing

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Write tests for your changes
  4. Ensure all tests pass (dotnet test)
  5. Commit your changes (git commit -m 'Add amazing feature')
  6. Push to the branch (git push origin feature/amazing-feature)
  7. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Disclaimer

This is an unofficial library and is not affiliated with NovelAI. Please ensure you comply with NovelAI's terms of service when using this library.