Создание мультистраничных альбомов TIFF** прямо с облачного хранилища является отличным способом архивирования или обмена большими наборами изображений (сканирования, фотографии продукции, страничные изображения).С помощью Aspose.Imaging для .NET вы можете stream картинки из Azure Blob Storage (или S3), конвертируя их в рамки TifF, и сохранять единый, компрессированный мультимедийный файлы T IFF — не требуются темпы.
Эта статья заменяет грыжу полным, встраиваемым, копирующим примером и добавляет точные детали для ТИФФ опций, компрессион , *ДПИ и пользования памяти.
Полный пример (Inline, Copy-Paste Ready)
Что делает эта программа:
- Список изображений в контейнере Azure Blob Storage (фильтр по расширению).
- Поток каждого блоба в память (без температурных файлов).
- Создает multi-page TIFF с использованием компрессии LZW при 300 DPI.
- Сберегает TIFF на локальный диск ** и** (опционально) загружает его обратно в контейнер.
Требования:
- .NET 8 (или 6+)
- Новые пакеты:-
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;
}
}
Почему эти варианты?
• Компрессия * :
TiffLzwRgb
обеспечивает *безполезную компрессию и высокую совместимость (идеально подходит для архивирования или обмена).Альтернативные варианты:
TiffDeflateRgb
(часто меньше, нуждается в поддержке Deflate); билевельные сканирования →TiffCcittFax4
.• ДПИ *:
ResolutionSetting(300, 300)
является друкованным для сканирования; выберите 150 для веб-само для уменьшения размеров.«Память» *
RasterImage.CacheData()
улучшает производительность, потому что исходные пиксели скрываются перед копией рамки.Заказ: Сортирование имен блоб обеспечивает стабильный порядок страницы (например,
page_001…page_999
).
Скачать альбом обратно в облак
Шаблон ** переходит на локальный диск** и немедленно загружает обратно с помощью одного и того же контейнера.Если ваш рабочий поток должен полностью избегать локальных файлов, направляйте TIFF в MemoryStream
и звонить UploadAsync
Для очень больших альбомов, предпочтительнее сэкономить на временном файле, чтобы сохранить использование памяти предсказуемо.
Amazon S3 вариант (сниптт)
Если вы на S3, то логика одна и та же — заменить звонки Azure SDK с звонками AWSSDK:
// 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)
}
Дайте Aspose.Imaging части идентичны; только listing/downloading код меняется.
Управление ошибками и устойчивость
- Пустый контейнер/префикс: приложение выходит милостиво с сообщением.
- Коррумпированное изображение*: вверх
Image.Load
В Аtry/catch
· пересекать плохие рамки и продолжать аборт на основе политики.
- Коррумпированное изображение*: вверх
- Слишком большие наборы: рассмотрите, как бросать (например, создавать один TIFF на 1000 изображений) для ограничения размеров файлов и границ сканера/инструмента.
- Название файла: включает дату/время или предпочтение в названии выхода для отслеживаемости (например,
album-2025-07-03_1500.tiff
).
Лучшие практики
- Конзистентные измерения: смешанные ориентации/показания хороши, но для унифицированных результатов предварительно нормализуются изображения (скала/ротация) перед копией рамки.
- ** Цветная глубина**: сканирование текста может лучше компрессировать, если оно конвертировано в грязную скалу перед сборкой TIFF (используйте фильтры Aspose.Imaging).
- Метаданные: Вы можете добавить EXIF/IPTC/XMP по рамке, прежде чем сохранять, если это необходимо.
- Тестирование: проверка выхода в нескольких зрителях (Windows Photos, IrfanView, Preview, ImageMagick) и с потребителями низкого потока (DMS, PACS и т.д.).
Заключение
Теперь у вас есть тестированный шаблон для создания многостраничных альбомов TIFF непосредственно из Azure Blob Storage (и легко переносится до S3). Пример сохраняет использование памяти предсказуемо, использует безполезную LZW компрессию и устанавливает практический 300 DPI по умолчанию – готов к архивированию, обмену и печати.
Клонируйте вышеупомянутый код в ваш проект, проводите в вашем контейнере и вы получите продуктивные альбомы TIFF за несколько минут.