בעידן הדיגיטלי של היום, ניהול תמונה יעיל הוא קריטי עבור יישומי אינטרנט ו- APIs.אחד היבטים העיקריים של ניהול התמונה הוא הדחיסה, אשר מסייע בהפחתת גודל הקובץ מבלי להפריע באופן משמעותי באיכות.המדריך הזה מלווה אותך דרך בניית API דינמי של דחיסת תמונות באמצעות Aspose.Imaging עבור .NET.

Aspose.Imaging היא ספרייה חזקה לעבודה עם תמונות ב- .NET. היא תומכת בפורמטים רבים ומספקת תכונות מניפולציה חזקות, כולל זרימת עבודה ללא הפסדים (JPEG) ו- PNG.

מה תבנה

  • נקודת סיום *: POST /api/images/compress?format=jpeg&quality=75&maxWidth=1280&maxHeight=1280
  • הכנסות: קובץ מרובה חלקים (תמונה), פרמטרים חיפוש אופציונליים עבור פורמט / איכות / גודל
  • יציאות: זרימת תמונה מצומצמת עם נכון Content-Type ראשי » Caching Headers
  • אבטחה: אימות סוג תוכן, גבולות גודל, ו- decode / encode שומרים

דרישות

  • תגית: .NET 8 (או .Net 6+)
  • פרוייקט ASP.NET Core Web API
  • נוגט : Aspose.Imaging
  • אופציונלי: החלפת רישיון באפליקציה סטארט-אפ (אם אתה משתמש בבניין מורשה)

מבנה הפרויקט (מינימום)

/Controllers
  ImageController.cs
/Services
  ImageCompressionService.cs
/Models
  CompressionRequest.cs
Program.cs
appsettings.json

דוגמה מלאה (Service + Controller)

להחליף את שטחי השם של המארח עם שטח השמות של הפרויקט שלך.

/Models/CompressionRequest.cs

namespace ImageApi.Models;

public sealed class CompressionRequest
{
    // "jpeg" or "png"
    public string Format { get; init; } = "jpeg";

    // 1..100 (applies to JPEG only; PNG is lossless)
    public int? Quality { get; init; } = 80;

    // Optional resize bounds; image is resized preserving aspect ratio if either is provided.
    public int? MaxWidth { get; init; }
    public int? MaxHeight { get; init; }

    // If true, strip metadata (EXIF, IPTC) where applicable to reduce size further.
    public bool StripMetadata { get; init; } = true;

    // Guardrails
    public void Validate()
    {
        var fmt = Format?.ToLowerInvariant();
        if (fmt is not "jpeg" and not "png")
            throw new ArgumentException("Unsupported format. Use 'jpeg' or 'png'.");

        if (Quality is { } q && (q < 1 || q > 100))
            throw new ArgumentException("Quality must be between 1 and 100.");

        if (MaxWidth is { } w && w <= 0) throw new ArgumentException("MaxWidth must be positive.");
        if (MaxHeight is { } h && h <= 0) throw new ArgumentException("MaxHeight must be positive.");
    }
}

/Services/ImageCompressionService.cs

using Aspose.Imaging;
using Aspose.Imaging.ImageOptions;
using ImageApi.Models;

namespace ImageApi.Services;

public interface IImageCompressionService
{
    Task<(MemoryStream output, string contentType, string fileExt)> CompressAsync(
        Stream input, CompressionRequest req, CancellationToken ct = default);
}

public sealed class ImageCompressionService : IImageCompressionService
{
    private readonly ILogger<ImageCompressionService> _logger;

    public ImageCompressionService(ILogger<ImageCompressionService> logger)
    {
        _logger = logger;
    }

    public async Task<(MemoryStream output, string contentType, string fileExt)> CompressAsync(
        Stream input, CompressionRequest req, CancellationToken ct = default)
    {
        req.Validate();

        // Defensive copy to a seekable stream
        var inbound = new MemoryStream();
        await input.CopyToAsync(inbound, ct).ConfigureAwait(false);
        inbound.Position = 0;

        // Load image via Aspose.Imaging
        using var image = Image.Load(inbound);

        // Optional: strip metadata (where applicable)
        if (req.StripMetadata)
        {
            TryStripMetadata(image);
        }

        // Optional resize (preserve aspect ratio)
        if (req.MaxWidth.HasValue || req.MaxHeight.HasValue)
        {
            ResizeInPlace(image, req.MaxWidth, req.MaxHeight);
        }

        // Choose encoder and options
        string fmt = req.Format.ToLowerInvariant();
        var (options, contentType, ext) = BuildOptions(fmt, req.Quality);

        // Save to output
        var output = new MemoryStream();
        image.Save(output, options);
        output.Position = 0;

        _logger.LogInformation("Compressed image to {Bytes} bytes as {Ext}", output.Length, ext);
        return (output, contentType, ext);
    }

    private static void ResizeInPlace(Image image, int? maxW, int? maxH)
    {
        var w = image.Width;
        var h = image.Height;

        double scaleW = maxW.HasValue ? (double)maxW.Value / w : 1.0;
        double scaleH = maxH.HasValue ? (double)maxH.Value / h : 1.0;
        double scale = Math.Min(scaleW, scaleH);

        if (scale < 1.0)
        {
            int newW = Math.Max(1, (int)Math.Round(w * scale));
            int newH = Math.Max(1, (int)Math.Round(h * scale));
            image.Resize(newW, newH);
        }
    }

    private static (ImageOptionsBase options, string contentType, string ext) BuildOptions(string fmt, int? quality)
    {
        switch (fmt)
        {
            case "jpeg":
            {
                var q = quality ?? 80;
                var jpeg = new JpegOptions { Quality = q };
                return (jpeg, "image/jpeg", "jpg");
            }
            case "png":
            {
                // PNG is lossless; using defaults ensures broad compatibility.
                // Many PNG tunables exist, but defaults are safe and effective.
                var png = new PngOptions();
                return (png, "image/png", "png");
            }
            default:
                throw new ArgumentOutOfRangeException(nameof(fmt), "Unsupported format.");
        }
    }

    private static void TryStripMetadata(Image image)
    {
        try
        {
            // Not every format exposes EXIF/IPTC the same way; a best-effort clear:
            if (image is RasterImage raster)
            {
                raster.RemoveAllFonts();
                raster.SetPropertyItems(Array.Empty<PropertyItem>());
            }
        }
        catch
        {
            // Non-fatal; ignore if format doesn't support these operations
        }
    }
}

Notes

  • JpegOptions.Quality (1–100) שולט בלחץ אובדן.
  • הפגמים של PNG הם בדרך כלל טוב עבור גרסה ראשונה; אם אתה צריך פגומים קטנים יותר, אתה יכול להוסיף טונינג מתקדם מאוחר יותר.
  • TryStripMetadata זהו גישה המאמץ הטוב ביותר; APIs metadata משתנים לפי פורמט מקור.

/Controllers/ImageController.cs

using ImageApi.Models;
using ImageApi.Services;
using Microsoft.AspNetCore.Mvc;

namespace ImageApi.Controllers;

[ApiController]
[Route("api/images")]
public sealed class ImageController : ControllerBase
{
    private static readonly HashSet<string> AllowedContentTypes = new(StringComparer.OrdinalIgnoreCase)
    {
        "image/jpeg", "image/png", "image/gif", "image/webp", "image/bmp", "image/tiff"
    };

    private readonly IImageCompressionService _svc;
    private readonly ILogger<ImageController> _logger;

    public ImageController(IImageCompressionService svc, ILogger<ImageController> logger)
    {
        _svc = svc;
        _logger = logger;
    }

    // POST /api/images/compress?format=jpeg&quality=75&maxWidth=1280&maxHeight=1280
    [HttpPost("compress")]
    [RequestSizeLimit(25_000_000)] // 25 MB cap; adjust to your needs
    public async Task<IActionResult> Compress(
        [FromQuery] string? format,
        [FromQuery] int? quality,
        [FromQuery] int? maxWidth,
        [FromQuery] int? maxHeight,
        [FromQuery] bool stripMetadata = true,
        IFormFile? file = null,
        CancellationToken ct = default)
    {
        if (file is null || file.Length == 0)
            return BadRequest("No file uploaded.");

        if (!AllowedContentTypes.Contains(file.ContentType))
            return BadRequest("Unsupported content type. Upload a common raster image (JPEG, PNG, GIF, WebP, BMP, TIFF).");

        var req = new CompressionRequest
        {
            Format = string.IsNullOrWhiteSpace(format) ? "jpeg" : format!,
            Quality = quality,
            MaxWidth = maxWidth,
            MaxHeight = maxHeight,
            StripMetadata = stripMetadata
        };

        await using var input = file.OpenReadStream();
        var (output, contentType, ext) = await _svc.CompressAsync(input, req, ct);

        // Strong caching for immutable responses (tune for your app/CDN)
        Response.Headers.CacheControl = "public,max-age=31536000,immutable";

        return File(output, contentType, fileDownloadName: BuildDownloadName(file.FileName, ext));
    }

    private static string BuildDownloadName(string originalName, string newExt)
    {
        var baseName = Path.GetFileNameWithoutExtension(originalName);
        return $"{baseName}-compressed.{newExt}";
    }
}

Program.cs (הרשמה + רישיון אופציונלי)

using Aspose.Imaging;
using ImageApi.Services;

var builder = WebApplication.CreateBuilder(args);

// Optional: initialize Aspose license from a file or stream if you have one
// var license = new Aspose.Imaging.License();
// license.SetLicense("Aspose.Total.lic");

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton<IImageCompressionService, ImageCompressionService>();

var app = builder.Build();

app.UseRouting();
app.UseAuthorization();
app.MapControllers();

// Enable for local testing
app.UseSwagger();
app.UseSwaggerUI();

app.Run();

הדרכה צעד אחר צעד

שלב 1: הגדרת הפרויקט

יצירת פרויקט ASP.NET Core Web API. Aspose.Imaging תגית: ליצור את Models, Services, ו Controllers קבצים כפי שהוצג לעיל.

שלב 2: הגדרת Aspose.Imaging (רישיון אופציונלי)

אם יש לך רישיון, להתחיל את זה ב-Startup (ראה Program.csזה מונע הערכת מדד המים ומבטיח פונקציונליות מלאה.

שלב 3: יישום שירות הדחיסה

The ImageCompressionService:

  • להעלות תמונות דרך Image.Load(Stream)
  • אופציונלי לגרד metadata
  • חידוש אופציונלי עם יחס היבט נשמר
  • חיסכון ב- JPEG או PNG עם אפשרויות מתאימות לתבנית

שלב 4: הקמת ה- API Controller

ImageController התצוגה POST /api/images/compress לקחת קובץ ושאלות פרמטרים:

  • format: jpeg או png (הדפדפן jpeg)
  • quality1 – 100 (JPEG בלבד; כברירת מחדל 80)
  • maxWidth/maxHeight• גבולות להורדה
  • stripMetadataתגית: default true תוצאות קטנות יותר

שלב 5: לבדוק את ה- API

השתמש בכל לקוח HTTP כדי לשלוח multipart/form-data בקשה עם שדה קובץ יחיד בשם file, plus optional query parameters. לבדוק:

  • Response Content-Type פורמט המשחקים
  • גודל הקובץ החוזר מופחת
  • מחדש את העבודה כפי שציפינו

בחירות עיצוב ושיטות הטוב ביותר

  • הגדרות בודקות פורמט: JPEG משתמשים QualityPNG נשאר ללא הפסדים עבור תוצר צפוי.
  • Downscale before encoding: Resizing reduces pixels first (הגודל הגדול ביותר מנצח), ולאחר מכן הקוד קצר bytes עוד יותר.
  • הכנסות סניטיז: סוג תוכן שומרים, גודל הקובץ, הגבולות של השאלות.
    • זרם**: הימנע לקרוא את הקובץ כולו לתוך הזיכרון שוב ושוב; שמור את הזרמים קצרים וניתן לחפש.
  • Caching: מסמן את התגובה כבלתי משתנה אם אתה מעורר שם/תוכן מתוך כניסות קבועות; אחרת להדביק את כותרות ה-Cache למקרה השימוש שלך.
  • אבטחה: להבטיח את סוג התוכן ולדחות תשלומים חשודים.
  • התבוננות: גודל הקלטות לפני/אחרי והפרמטרים המשמשים; זה עוזר לך לנקות את הבדלים.
  • התעללות: אם מופיע בפומבי, הגבלת הריבית או דורש auth כדי למנוע התעלמות.

הרחבות נפוצות (להוריד מאוחר יותר)

  • קודרים WebP/AVIF עבור תמונות קטנות יותר (הוספת אפשרויות חדשות/contentType/הרחבה של הקובץ BuildOptions).
  • PNG tuning (רמת מסנן / דחיסה) אם אתה זקוק נכסים קטנים נוספים ללא הפסדים.
    • הפרופיל הקודם * כמו thumbnail, preview, hires להגדיר פרמטרים ידועים.
  • ** ETags** או hashing תוכן כדי לשרת תשובות זהות מ- cache.
  • ** Async batch** endpoint כדי לחתוך קבצים מרובים בו זמנית.

Troubleshooting

    • יציאה גדולה *: הגדלת RequestSizeLimit או זרם לאחסון טמ.
    • צבעים *: אזור צבע מאובטח מטופל על פי דפוסים; במקרים מתקדמים ייתכן שיהיה צורך בצבע מפורש.
  • אין הפחתת גודל (PNG): PNG הוא ללא הפסדים; מאפשר מחדש או לעבור ל- JPEG עבור חיסכון ביט חזק יותר.

סיכום

עכשיו יש לך API דינמי להדביק תמונה מוכנה לייצור באמצעות Aspose.Imaging. הפיקוד מנהל את ההעלאות ואת הפרמטרים; השירות משתמש בטוח, מודעות פורמט הדביקה ואפשרות מחדש, ולאחר מכן זורם בחזרה תשובה טופס כראוי עם כותרות כתיבה.

More in this category