Mencipta album TIFF berbilang halaman secara langsung dari penyimpanan awan adalah cara yang bagus untuk mengarkibkan atau bertukar set gambar besar (scans, foto produk, gambar halaman). Dengan Aspose.Imaging untuk .NET, Anda dapat stream gambar dari Azure Blob Storage (atau S3), menukarkan mereka ke bingkai TifF, dan menyimpan satu, dikompresi multi-page tiff – tidak ada file temp yang diperlukan.

Artikel ini menggantikannya dengan ** lengkap, inline, copy-paste contoh** dan menambahkan rincian yang tepat untuk TIFF opsi, kompresi , DPI serta penggunaan memori.

Contoh lengkap (Inline, Copy-Paste Ready)

Apa yang dilakukan program ini:

  • Daftar gambar dalam kontena Azure Blob Storage (filter dengan perpanjangan).
  • Setiap blok mengalir ke dalam memori (tidak ada file temp).
  • Membangun TIFF berbilang halaman menggunakan kompresi LZW pada 300 DPI.
  • Menyimpan TIFF ke cakera lokal ** dan** (optional) memuatkannya kembali ke wadah.

Persyaratan:

  • .NET 8 (atau 6+)
  • Pakej dari 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;
    }
}

Mengapa pilihan ini?

  • • Kompresi * : TiffLzwRgb memberikan kompresi tanpa kerugian dan kompatibilitas yang tinggi (ideal untuk penyimpanan atau pertukaran).

  • Alternatif yang : TiffDeflateRgb (sering lebih kecil, membutuhkan dukungan Deflate); pemindaian bilevel → TiffCcittFax4.

    • Untuk DPI : ResolutionSetting(300, 300) dicetak untuk pemindaian; pilih 150 untuk web-hanya untuk mengurangi ukuran.
  • • Memori * : RasterImage.CacheData() Meningkatkan kinerja karena piksel sumber tersembunyi sebelum salinan frame.

  • Ordering: Mengurutkan nama blob memastikan urutan halaman yang stabil (misalnya, page_001…page_999).

Download album kembali ke awan

Sampel melepaskan ke cakera lokal dan segera upload kembali menggunakan kontena yang sama.Jika aliran kerja Anda harus menghindari file lokal sepenuhnya, sirkuit TIFF ke MemoryStream dan panggilan UploadAsync Untuk album yang sangat besar, lebih baik simpan pada file temporary untuk menjaga penggunaan memori yang dapat diramalkan.

Spesifikasi Amazon S3 (Snippet)

Jika Anda berada di S3, logika yang sama—mengganti panggilan Azure SDK dengan panggungan 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)
}

Pastikan bagian-bagian Aspose.Imaging identik; hanya perubahan kode listing/downloading.

Kesalahan dan Resiliensi

    • Kontainer kosong/prefix**: aplikasi keluar dengan baik dengan pesan.
    • Gambar yang rusak* : Wrap Image.Load Dalam A try/catch· melepaskan kerangka buruk dan melanjutkan, atau aborsi berdasarkan kebijakan.
  • Set yang sangat besar*: pertimbangkan untuk menghitung (misalnya, membuat satu TIFF per 1.000 gambar) untuk membatasi ukuran file dan batasan scanner/tool.
  • ** Nama file**: termasuk tanggal/waktu atau prefix dalam nama output untuk traceability (misalnya, album-2025-07-03_1500.tiff).

Praktik Terbaik

  • Dimensi konsisten: orientasi bercampur/size bagus, tetapi untuk hasil yang seragam, gambar pra-normalisasi (scale/rotat) sebelum salinan bingkai.
  • Dalam warna: pemindaian teks dapat dikompresi lebih baik jika ditukar ke skala abu-abu sebelum pengaturan TIFF (menggunakan filter Aspose.Imaging).
  • Metadata: Anda dapat melampirkan EXIF/IPTC/XMP per frame sebelum menyimpan jika perlu.
  • Testing: verify output dalam multiple viewers (Windows Photos, IrfanView, Preview, ImageMagick) dan dengan downstream konsumen (DMS, PACS, dll).

Kesimpulan

Anda sekarang memiliki pola yang diuji** untuk membangun album TIFF berbilang halaman** langsung dari Azure Blob Storage (dan mudah dipindahkan ke S3). contoh ini mempertahankan penggunaan memori yang dapat diramalkan, menggunakan kompresi LZW tanpa kerugian**, dan menetapkan default yang praktis 300 DPI – siap untuk penyimpanan, pertukaran dan pencetakan.

Klon kode di atas ke dalam proyek Anda, kabel di string koneksi / kontena, dan Anda akan memiliki produk-grade album TIFF dalam beberapa menit.

More in this category