- C# 100%
|
All checks were successful
CI / build (push) Successful in 26s
Reviewed-on: #4 |
||
|---|---|---|
| .claude | ||
| .github | ||
| samples/NovelAI.ImageGen.Sample | ||
| src/NovelAI.ImageGen | ||
| tests/NovelAI.ImageGen.Tests | ||
| .gitignore | ||
| CHANGELOG.md | ||
| CODEOWNERS | ||
| Directory.Build.props | ||
| Directory.Packages.props | ||
| LICENSE | ||
| NovelAI.ImageGen.slnx | ||
| README.md | ||
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.
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
IHttpClientFactorysupport - 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 factoryMicrosoft.Extensions.Options- Options patternSixLabors.ImageSharp- Image processing
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Write tests for your changes
- Ensure all tests pass (
dotnet test) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - 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.