创建 多页 TIFF 专辑直接从云存储是一个很好的方式,存档或交换大图像集(扫描,产品照片,页面图片)。 使用 Aspose.Imaging for .NET,您可以从 Azure Blob Storage (或 S3),将它们转换为 TifF 框架,并保存一个单一,压缩的多个页TIF - 没有 temp 文件需要。

这篇文章用一个 完整,内线,复制的样本 取代了,并为 TIFF 选项, 压缩DPI记忆使用 的准确细节添加。

完整例子(Inline, Copy-Paste Ready)

这个计划做什么:

  • Azure Blob Storage 容器中列出图像(按扩展过滤)。
  • 每個泡沫都流入記憶體(沒有 temp 檔案)。
  • 使用 LZW 压缩在 300 DPI 的 **多页 TIFF 构建。
  • 将 TIFF 存储到本地磁盘 ** 并(可选)将其重新上传到容器中。

要求:

  • .NET 8(或 6+)
  • NuGet 包装:- 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 支持); bilevel 扫描 TiffCcittFax4.

  • ◎DPI*: ResolutionSetting(300, 300) 是打印友好的扫描;选择 150 仅为网格,以减少尺寸。

    • 記憶體 * : RasterImage.CacheData() 提高性能,因为源像素在框复制之前被隐藏。
  • 订单:分类 blob 名称确保稳定的页面顺序(例如, page_001…page_999).

下载专辑返回云端

样品 ** 将其转移到本地磁盘** 并立即使用相同容器重新加载** 如果您的工作流应该完全避免当地文件,则将 TIFF 传输到一个 MemoryStream 和呼叫 UploadAsync 对于非常大的专辑,最好节省到一个 临时文件 以保持内存使用可预测。

亚马逊 S3 版本(Snippet)

如果您在 S3 上,逻辑是相同的 - 将 Azure SDK 通话替换为 AWS SDk 电话:

// 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 的部分相同;只有 列表/下载 代码的变化。

错误处理与耐用性

  • 空容器/预定:该应用程序与消息相同地输出。
  • ** 腐败的图像**:包装 Image.Load 在A try/catch排除坏框架并继续,或根据政策堕胎。
  • ** 非常大的套件**:考虑点击(例如,每1000张图像创建一个 TIFF),以限制文件大小和扫描仪/工具限制。
  • 文件名称:在输出名中包含日期/时间或预定,以便可追踪(例如, album-2025-07-03_1500.tiff).

最佳实践

  • 一致的尺寸:混合的方向/差异很好,但对于均匀的结果,在框架复制之前预先正常化图像(规模/旋转)。
  • ** 颜色深度**:文本扫描可以更好地压缩,如果在 TIFF 组合之前转换为灰色(使用 Aspose.Imaging 过滤器)。
  • ** 数据**:您可以将 EXIF/IPTC/XMP 添加到每个框架中,如果需要,就可以保存。
  • 测试:在多个观众(Windows Photos、IrfanView、Preview、ImageMagick)和下流消费者(DMS、PACS等)中验证输出。

结论

您现在有一个 ** 测试模式** 可以直接从 ** Azure Blob Storage 创建 多页 TIFF 专辑 (易于传输到 ** S3**) 。 该示例将内存使用量保持可预测,使用 ** LZW 压缩**,并设置一个实用的 ** 300 DPI 默认版本 - 可存档、交换和打印。

将上面的代码集成到您的项目中,在您的连接带/容器中插入线条,您将在几分钟内获得生产级的TIFF专辑。

More in this category