클라우드 스토리지에서 직접 Multi-page TIFF 앨범을 만드는 것은 큰 이미지 세트 (스캔, 제품 사진, 페이지 이미지)를 아카이브하거나 교환하는 좋은 방법입니다. Aspose.Imaging for .NET를 사용하면 Azure Blob Storage (또는 S3)에서 stream 이미지를 만들 수 있습니다.

이 문서에서는 완전한, 인라인, 복사 패스트 예제로 을 대체하고 TIFF 옵션, 압축DPI 사용에 대한 정확한 세부 사항을 추가합니다.

완전한 예제 (Inline, Copy-Paste Ready)

이 프로그램은 무엇을합니다 :

    • Azure Blob Storage 컨테이너에 있는 이미지 목록( 확장에 따라 필터링)
  • 각 블록을 메모리로 흐르십시오 (템 파일이 없습니다).
  • LZW 압축을 사용하여 Multi-page TIFF를 만듭니다 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 (일반적으로 작은, 데플라이트 지원이 필요합니다); 벨벨 스캔 → TiffCcittFax4.

    • 가정용 가치* : ResolutionSetting(300, 300) 스캔을 위해 인쇄 친화적입니다; 크기를 줄이기 위해 온라인으로 150를 선택합니다.
    • 메모리 * : RasterImage.CacheData() 출처 픽셀이 프레임 복사 전에 암호화되기 때문에 성능을 향상시킵니다.
  • ** 주문**: 블로브 이름을 분류하면 안정적인 페이지 순서를 보장합니다 (예를 들어, page_001…page_999).

앨범을 다시 클라우드로 업로드합니다.

샘플 가 현지 디스크로 옮겨지고 즉시 **이 컨테이너를 사용하여 다시 업로드됩니다. 작업 흐름이 지역 파일을 완전히 피해야하는 경우 TIFF를 전송합니다. MemoryStream 그리고 전화를 UploadAsync 매우 큰 앨범의 경우 메모리 사용을 예측할 수 있도록 시간 파일에 저장하는 것을 선호합니다.

아마존 S3 변형 (스니프트)

당신이 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 부품을 동일하게 유지하십시오; listing/downloading 코드만 변경합니다.

오류 처리 및 저항성

  • ** 빈 컨테이너/전체*: 앱은 메시지와 함께 은혜롭게 출력합니다.
    • 부패한 이미지* : 포장 Image.Load 에 A try/catch· 나쁜 프레임을 놓고 계속, 또는 정책 기반 낙태.
  • 매우 큰 세트: 파일 크기와 스캐너 / 도구 한도를 제한하기 위해 (예를 들어, 1,000 이미지 당 하나의 TIFF를 구축) 탐색을 고려하십시오.
  • ** 파일 이름** : 추적 가능성에 대한 출력 이름에 날짜/시간 또는 사전을 포함합니다 (예 : album-2025-07-03_1500.tiff).

최고의 관행

  • ** 일관된 크기**: 혼합된 방향/형은 훌륭하지만, 유일한 결과는 프레임 복사 전에 이미지를 사전 정상화합니다.
  • 색상 깊이: 텍스트 스캔은 TIFF 조립 전에 그레이 스케일로 변환되면 더 잘 압축 될 수 있습니다 (Aspose.Imaging 필터를 사용).
  • 메타데이터: 필요한 경우 저장하기 전에 프레임 당 EXIF/IPTC/XMP을 연결할 수 있습니다.
  • ** 테스트**: 수많은 시청자 (Windows Photos, IrfanView, Preview, ImageMagick) 및 다운로드 소비자(DMS, PACS 등)에서 출력을 확인합니다.

결론

이제 ** 테스트된 패턴**은 Azure Blob Storage에서 직접 Multi-page TIFF 앨범을 만들 수 있습니다.이 예제는 메모리 사용을 예측할 수 있게 유지하고, **LZW 압축을 사용하며, 실용적인 300 DPI 기본을 설정합니다. - 아카이브, 교환 및 인쇄를 위한 준비가 되었습니다.

위의 코드를 프로젝트에 클론하고, 연결 스트립/컨테이너에 끈을 넣고, 몇 분 안에 생산 수준의 TIFF 앨범이 있습니다.

More in this category