Skapa multi-page TIFF album direkt från molnlagring är ett bra sätt att arkivera eller utbyta stora bilder (skanningar, produktfoton, sidbilder). Med Aspose.Imaging för .NET kan du stream bilder från Azure Blob Storage (eller S3), konvertera dem till TifF-ramar, och spara en enda, komprimerade multi-sidor TFF – inga temp filer krävs.
I den här artikeln ersätts gemenskapen med ett komplett, inline, kopierad-paste-exempel och läggs till exakta detaljer för TIFF-alternativ, ** komprimering** , DPI samt minneanvändning.
Komplett Exempel (Inline, Copy-Paste Ready)
Vad detta program gör:
- Lista bilder i en Azure Blob Storage behållare (filtrering genom förlängning).
- Stream varje blob in i minnet (ingen temp filer).
- Bygg en multi-page TIFF med LZW kompression vid 300 DPI.
- Spara TIFF till lokalskivan ** och** (alternativt) laddar den tillbaka till behållaren.
Krav:
- .NET 8 (eller 6+)
- NuGet förpackningar:-
Aspose.Imaging
Azure.Storage.Blobs
// File: Program.cs
// Build deps:
// dotnet add package Aspose.Imaging
// dotnet add package Azure.Storage.Blobs
//
// Run (example):
// setx AZURE_STORAGE_CONNECTION_STRING "<your-connection-string>"
// dotnet run -- "<container-name>" "album-output.tiff" // uploads album-output.tiff back to same container
//
// Notes:
// - Streams JPEG/PNG/BMP/TIFF/GIF web-safe inputs and assembles a multi-page LZW RGB TIFF at 300 DPI.
// - If there are no images, the program exits gracefully.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Azure;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Aspose.Imaging;
using Aspose.Imaging.FileFormats.Tiff;
using Aspose.Imaging.FileFormats.Tiff.Enums;
using Aspose.Imaging.ImageOptions;
class Program
{
// Accepted extensions (case-insensitive)
private static readonly string[] ImageExts = new[]
{
".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tif", ".tiff"
};
static async Task<int> Main(string[] args)
{
if (args.Length < 2)
{
Console.Error.WriteLine("Usage: dotnet run -- <containerName> <albumFileName.tiff> [prefix]");
Console.Error.WriteLine("Example: dotnet run -- scans album-2025-07.tiff scans/incoming/");
return 1;
}
string containerName = args[0];
string albumFileName = args[1];
string prefix = args.Length > 2 ? args[2] : string.Empty;
string? conn = Environment.GetEnvironmentVariable("AZURE_STORAGE_CONNECTION_STRING");
if (string.IsNullOrWhiteSpace(conn))
{
Console.Error.WriteLine("AZURE_STORAGE_CONNECTION_STRING is not set.");
return 2;
}
try
{
var container = new BlobContainerClient(conn, containerName);
// 1) Enumerate candidate image blobs (optionally under a prefix)
var images = await ListImageBlobsAsync(container, prefix);
if (images.Count == 0)
{
Console.WriteLine("No images found. Nothing to do.");
return 0;
}
Console.WriteLine($"Found {images.Count} image(s). Building multi-page TIFF…");
// 2) Build multipage TIFF in memory (for safety, stream to file to avoid huge RAM for very large sets)
// We will construct a TiffImage and append frames.
string localAlbumPath = Path.GetFullPath(albumFileName);
BuildMultipageTiffFromBlobs(container, images, localAlbumPath);
Console.WriteLine($"✅ Saved multi-page TIFF locally: {localAlbumPath}");
// 3) Optional: upload back to same container
var albumBlob = container.GetBlobClient(Path.GetFileName(albumFileName));
Console.WriteLine($"Uploading album back to container as: {albumBlob.Name} …");
using (var fs = File.OpenRead(localAlbumPath))
{
await albumBlob.UploadAsync(fs, overwrite: true);
}
Console.WriteLine("✅ Upload complete.");
return 0;
}
catch (RequestFailedException are)
{
Console.Error.WriteLine("Azure error: " + are.Message);
return 3;
}
catch (Exception ex)
{
Console.Error.WriteLine("Error: " + ex.Message);
return 4;
}
}
private static async Task<List<BlobItem>> ListImageBlobsAsync(BlobContainerClient container, string prefix)
{
var result = new List<BlobItem>();
await foreach (var item in container.GetBlobsAsync(prefix: prefix))
{
// Skip virtual folders
if (item.Properties.BlobType != BlobType.Block)
continue;
if (HasImageExtension(item.Name))
result.Add(item);
}
// Optional: stable order by name (e.g., page_001.jpg … page_999.jpg)
result.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase));
return result;
}
private static bool HasImageExtension(string blobName)
{
string ext = Path.GetExtension(blobName) ?? string.Empty;
return ImageExts.Contains(ext, StringComparer.OrdinalIgnoreCase);
}
private static void BuildMultipageTiffFromBlobs(BlobContainerClient container, List<BlobItem> images, string outputTiffPath)
{
// TIFF encoder defaults:
// - LZW compression is a good balance of size & compatibility for RGB images.
// - 300 DPI is print-friendly; change if you need web-only output.
var tiffOptions = new TiffOptions(TiffExpectedFormat.TiffLzwRgb)
{
ResolutionSettings = new ResolutionSetting(300, 300)
};
TiffImage? tiff = null;
try
{
for (int index = 0; index < images.Count; index++)
{
var blobClient = container.GetBlobClient(images[index].Name);
Console.WriteLine($"Downloading & adding: {blobClient.Name}");
using var ms = new MemoryStream();
blobClient.DownloadTo(ms);
ms.Position = 0;
// Load the image with Aspose.Imaging (auto-detects format)
using var src = Image.Load(ms);
// Cache pixel data to speed up frame copy (especially for network streams)
if (src is RasterImage raster)
raster.CacheData();
// Create a TIFF frame by copying from the source image
// NOTE: TiffFrame.CopyFrame(tiffOptions, <RasterImage>) is preferred when available
TiffFrame frame;
if (src is RasterImage rimg)
{
frame = TiffFrame.CopyFrame(tiffOptions, rimg);
}
else
{
// Fallback: render non-raster formats into a rasterized frame
frame = CreateRasterFrameFromAny(src, tiffOptions);
}
if (index == 0)
{
// First frame defines the TiffImage
tiff = new TiffImage(frame);
}
else
{
tiff!.AddFrame(frame);
}
}
if (tiff == null)
throw new InvalidOperationException("No frames were created. Aborting.");
// Save to local TIFF file
tiff.Save(outputTiffPath);
}
finally
{
tiff?.Dispose();
}
}
private static TiffFrame CreateRasterFrameFromAny(Image src, TiffOptions opts)
{
// Create a blank frame and draw the source into it
// This is a compatibility path if the loaded image isn’t a RasterImage
var frame = new TiffFrame(opts, src.Width, src.Height);
using (var graphics = new Aspose.Imaging.Graphics(frame))
{
graphics.Clear(Aspose.Imaging.Color.White);
graphics.DrawImage(src, new Aspose.Imaging.Rectangle(0, 0, src.Width, src.Height));
}
return frame;
}
}
Varför dessa alternativ?
• Komprimering *:
TiffLzwRgb
ger slös kompression och hög kompatibilitet (ideal för arkivering eller utbyte).Alternativa alternativ :
TiffDeflateRgb
(ofta mindre, behöver Deflate-stöd); bilevelskannor →TiffCcittFax4
.- och dp* :
ResolutionSetting(300, 300)
är tryckvänlig för skanningar; välj 150 för web-enbart för att minska storleken.
- och dp* :
- minnesmärke *
RasterImage.CacheData()
förbättrar prestanda eftersom källpixel krypteras före ramkopiering.
- minnesmärke *
Ordering: Sortera blob namn säkerställer en stabil sida order (t.ex.
page_001…page_999
).
Ladda ner albumet tillbaka till molnet
Provet sätts till den lokala skivan och omedelbart upload back med samma behållare. Om arbetsflödet bör undvika lokella filer helt, strömma TIFF till en MemoryStream
och ringer UploadAsync
För mycket stora album, föredrar du att spara på en temporary fil för att hålla minnesanvändningen förutsägbar.
Amazon S3 variant (snippet)
Om du är på S3 är logiken densamma – ersätta Azure SDK-samtal med AWS SDk- samtal:
// NuGet:
// dotnet add package AWSSDK.S3
using Amazon.S3;
using Amazon.S3.Model;
// Listing:
using var s3 = new AmazonS3Client(Amazon.RegionEndpoint.APSouth1);
var list = await s3.ListObjectsV2Async(new ListObjectsV2Request
{
BucketName = "your-bucket",
Prefix = "images/"
});
foreach (var obj in list.S3Objects.Where(o => HasImageExtension(o.Key)))
{
using var get = await s3.GetObjectAsync("your-bucket", obj.Key);
using var ms = new MemoryStream();
await get.ResponseStream.CopyToAsync(ms);
ms.Position = 0;
using var src = Image.Load(ms);
// (same TiffFrame.CopyFrame logic as above)
}
Håll Aspose.Imaging delar identiska; endast listing/downloading koden ändras.
Felhantering och motståndskraft
- Öppna behållare/prefix: appen går ut med ett meddelande.
- ** Korrupt bild**: Wrap
Image.Load
I Atry/catch
· hoppa bort dåliga ramar och fortsätta, eller abort baserat på politik. - Väldigt stora uppsättningar: överväga chunking (t.ex. skapa en TIFF per 1000 bilder) för att begränsa filstorlekar och scanner/verktygsgränser.
- Filnamn: Innehåller datum/tid eller prefix i utgångsnamnet för spårbarhet (t.ex.
album-2025-07-03_1500.tiff
).
Bästa praxis
- ** Konsistenta dimensioner**: blandade orienteringar/grader är fina, men för enhetliga resultat pre-normalisera bilder (skala/rotation) före ramkopiering.
- Färgdjup: Skanningar av text kan komprimera bättre om de konverteras till gråskala före TIFF-samlingen (använd Aspose.Imaging-filter).
- Metadata: Du kan bifoga EXIF/IPTC/XMP per ram innan du sparar om det behövs.
- Testing: verifiera utgången i flera tittare (Windows Photos, IrfanView, Preview, ImageMagick) och med nedströmsförbrukar (DMS, PACS, etc.).
slutsatser
Du har nu en testad mönster för att bygga Multi-page TIFF album direkt från Azure Blob Storage (och lätt bärbar till S3). exemplet håller minnesanvändningen förutsägbar, använder slös LZW kompression, och ställer in en praktisk 300 DPI standard – redo för arkivering, utbyte och utskrift.
Klona ovanstående kod i ditt projekt, tråd i din anslutning sträng / behållare, och du kommer att ha produktionsnivå TIFF album på några minuter.