إن إنشاء ألبومات TIFF متعددة الصفحات مباشرة من تخزين السحابة هو وسيلة رائعة لترتيب أو تبادل مجموعات الصور الكبيرة (المسح الضوئي، صور المنتج، صورة الصفحة).مع Aspose.Imaging لـ .NET، يمكنك تدفق الصور من Azure Blob Storage (أو S3)، وتحويلها إلى إطارات TifF، وتوفير مجموعة واحدة، مضاعفة من ملفات TIC - لا تتطلب الملفات السرعة.
ويستبدل هذا المقال الرماد بمثال كامل، في خط، نسخ-بست ويضيف تفاصيل دقيقة لـ خيارات TIFF، الضغط و DPI واستخدام الذاكرة**.
نموذج كامل (Inline, Copy-Paste Ready)
ماذا يفعل هذا البرنامج:
- قائمة الصور في حاوية Azure Blob Storage (تصفية حسب التمديد).
- تدفق كل كتلة إلى الذاكرة (لا ملفات Temp).
- إنشاء الصفحة المتعددة 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()
تحسين الأداء لأن البكسل المصدرية يتم تخزينها قبل نسخ الإطار.
- الذاكرة * :
الطلب: يضمن تصنيف أسماء blob ترتيب صفحة مستقرة (على سبيل المثال،
page_001…page_999
).
تحميل الألبوم مرة أخرى إلى السحابة
يذهب العينة ** إلى القرص المحلي** ويتم تحميلها على الفور ** مرة أخرى** باستخدام نفس الحاويات.إذا كان تدفق العمل الخاص بك يجب تجنب الملفات المحلية بالكامل، قم بتدفق TIFF إلى MemoryStream
وتدعو UploadAsync
بالنسبة للألبومات الكبيرة جدا، فمن الأفضل توفير إلى ملف ** مؤقت** للحفاظ على استخدام الذاكرة قابلة للتنبؤ.
آمازون S3 متغيرات (Snippet)
إذا كنت على 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 أجزاء مماثلة؛ فقط قائمة/تحميل تغير الرمز.
التعامل مع الأخطاء والقدرة على التحمل
- ** حاوية فارغة/مرفق**: يخرج التطبيق لطيفًا مع رسالة.
- الصورة الفاسدة: الارتباك
Image.Load
في أtry/catch
· التخلص من الإطارات السيئة والاستمرار، أو الإجهاض على أساس السياسة. - مجموعات كبيرة جدا: فكر في التشويش (على سبيل المثال، إنشاء TIFF واحد لكل 1000 صورة) للحد من حجم الملفات والحد الأقصى للمسح الضوئي/الأداة.
- ** اسم الملف**: يحتوي على تاريخ/وقت أو تعريف في اسم الناتج للتتبع (على سبيل المثال،
album-2025-07-03_1500.tiff
).
أفضل الممارسات
- أبعاد متسقة: اتجاهات مختلطة/مختلفة جيدة، ولكن للحصول على نتائج موحدة، يتم تحديد الصور المسبقة (المقياس/التدفق) قبل نسخ الإطار.
- عمق الألوان: يمكن فحص النص بشكل أفضل إذا تم تحويلها إلى مقياس رمادي قبل تجميع TIFF (استخدام مرشحات Aspose.Imaging).
- Metadata: يمكنك إرفاق EXIF/IPTC/XMP لكل إطار قبل حفظه إذا لزم الأمر.
- اختبار: التحقق من النتيجة في العديد من المشاهدين (Windows Photos، IrfanView، Preview، ImageMagick) ومع المستخدمين المنخفض (DMS، PACS، إلخ).
استنتاجات
لديك الآن نموذج مختبر لإنشاء ألبومات TIFF متعددة الصفحات مباشرة من Azure Blob Storage (ويمكن نقلها بسهولة إلى S3).المثال يحافظ على استخدام الذاكرة قابل للتنبؤ، ويستخدم الضغط LZW الخالي من الخسارة، ويتم وضع الافتراض العملي 300 DPI – جاهز للأرشيف والتبادل والطباعة.
استنساخ الرمز المذكور أعلاه في مشروعك، وإدخال الأسلاك في شريط الاتصال / حاوية، وستحصل على ألبومات TIFF من الدرجة الإنتاجية في دقائق.