کیفیت بهتر همیشه به معنی فایل بزرگ‌تر نیست

کاربر وقتی از بهبود کیفیت عکس حرف می‌زند، ممکن است منظورش عکس تار، جهت اشتباه، متن ناخوانا یا فشرده‌سازی شدید باشد. پیش از انتخاب ابزار، علت را مشخص کنید. بزرگ‌کردن یک تصویر کوچک به‌تنهایی جزئیات واقعیِ ثبت‌نشده را برنمی‌گرداند. برای سایت، کیفیت مناسب یعنی موضوع عکس در اندازهٔ نمایش واضح باشد و فایل بی‌دلیل سنگین نشود؛ این دو هدف باید کنار هم بررسی شوند، نه در دو مرحلهٔ بی‌ارتباط.

نمونه ابتدا جهت ذخیره‌شده در اطلاعات دوربین را اعمال می‌کند و سپس تصویر را بدون بزرگ‌نمایی به سقف اندازهٔ تعیین‌شده می‌رساند. خروجی در قالب وب‌پی با کیفیت انتخابی ذخیره می‌شود و فایل موجود بازنویسی نمی‌شود. تصاویر متحرک و تصویرهای بیش از سقف پیکسل پذیرفته نیستند. این ابزار برای فایل‌های منتخب مدیر محتواست؛ اعتبارسنجی دریافت فایل از کاربر، محدودیت درخواست و کنترل دسترسی مسئلهٔ جداگانه‌ای است.

جهت، اندازه و فشرده‌سازی را جدا کنترل کنید

کیفیت عددی یک رمزگذار، درصد حفظ جزئیات یا امتیاز استاندارد مشترک میان همهٔ قالب‌ها نیست. برای همین سه خروجی با تنظیم متفاوت می‌سازیم و حجم و اختلاف متوسط کانال‌های رنگ را می‌سنجیم. این اختلاف، معیار کامل ادراک انسانی نیست و بافت، نوشته و لبه‌های حساس را به‌تنهایی توصیف نمی‌کند. تصویر واقعی، پروفایل رنگ و نحوهٔ نمایش روی صفحه باید با بازبینی دیداری هم بررسی شوند.

چهار فایل را کنار هم ذخیره کنید و بستهٔ Pillow را در محیط مجازی نصب کنید. ابتدا python -m unittest test_optimize.py را اجرا کنید. سپس python make_sample.py و python compare.py را اجرا کنید؛ سه فایل کیفیت مقایسه‌ای ساخته می‌شود. فایل نمونه صرفاً برای تکرار آزمایش تولید شده است. اجرای دوباره در همان پوشه، به‌جای بازنویسی خروجی موجود خطا می‌دهد تا نتیجهٔ قبلی ناخواسته از بین نرود.

آزمون‌ها جلوگیری از بزرگ‌نمایی، حفظ مقصد موجود و اصلاح جهت دوربین را بررسی می‌کنند. برای عکس واقعی، در بزرگ‌نمایی صددرصد و اندازهٔ نمایش سایت، متن و لبه‌ها را ببینید. اگر تصویر از ابتدا خارج از فوکوس است، دنبال اصل باکیفیت‌تر باشید. رنگ‌های حساس برند یا چاپی نیز ممکن است به مدیریت دقیق پروفایل رنگ نیاز داشته باشند؛ تبدیل سادهٔ حالت رنگ نمونه، تضمین تطبیق کامل همهٔ پروفایل‌ها نیست.

فایل‌های کامل و قابل اجرا

چهار فایل زیر کامل‌اند؛ پیش‌نیاز، ترتیب اجرا و روش بررسی در متن آمده است. کدها خلاصه یا پنهان نشده‌اند.

1. فایل کامل optimize.py

این فایل را با همین نام و بدون حذف سطرها کنار بقیهٔ فایل‌ها ذخیره کنید.

from pathlib import Path
from PIL import Image, ImageOps


def optimize(source, destination, max_width=1200, quality=85):
    source, destination = Path(source), Path(destination)
    if not 1 <= max_width <= 4000 or not 60 <= quality <= 95 or destination.suffix.lower() != '.webp':
        raise ValueError('Use width 1..4000, quality 60..95 and a WebP destination')
    with Image.open(source) as original:
        if getattr(original, 'is_animated', False) or original.width * original.height > 20_000_000:
            raise ValueError('Use a single image of at most 20 million pixels')
        image = ImageOps.exif_transpose(original)
        image.thumbnail((max_width, 4000), Image.Resampling.LANCZOS)
        image = image.convert('RGBA' if 'A' in image.getbands() else 'RGB')
        with destination.open('xb') as output:
            image.save(output, format='WEBP', quality=quality, method=6)
    return {'width': image.width, 'height': image.height, 'bytes': destination.stat().st_size}

دستور اجرا، نتیجهٔ مورد انتظار و محدودیت این فایل در بخش اجرای مقاله توضیح داده شده است.

2. فایل کامل make_sample.py

این فایل را با همین نام و بدون حذف سطرها کنار بقیهٔ فایل‌ها ذخیره کنید.

from pathlib import Path
from PIL import Image, ImageDraw


def create_sample(path):
    image = Image.new('RGB', (1600, 900), '#eae9e3')
    draw = ImageDraw.Draw(image)
    for x in range(0, 1600, 8):
        draw.line((x, 0, x, 900), fill=(x % 256, 80, 130), width=3)
    draw.rectangle((200, 250, 1400, 650), outline='#15342b', width=5)
    with Path(path).open('xb') as output:
        image.save(output, format='PNG')


if __name__ == '__main__': create_sample('sample.png')

دستور اجرا، نتیجهٔ مورد انتظار و محدودیت این فایل در بخش اجرای مقاله توضیح داده شده است.

3. فایل کامل compare.py

این فایل را با همین نام و بدون حذف سطرها کنار بقیهٔ فایل‌ها ذخیره کنید.

from pathlib import Path
from PIL import Image, ImageChops, ImageOps, ImageStat
from optimize import optimize

if __name__ == '__main__':
    source = Path('sample.png')
    for quality in (75, 85, 95):
        target = Path(f'sample-q{quality}.webp')
        result = optimize(source, target, max_width=960, quality=quality)
        with Image.open(source) as original, Image.open(target) as encoded:
            reference = ImageOps.exif_transpose(original).convert('RGB')
            reference.thumbnail(encoded.size, Image.Resampling.LANCZOS)
            error = ImageStat.Stat(ImageChops.difference(reference, encoded.convert('RGB'))).mean
        print({'quality': quality, **result, 'meanAbsoluteChannelError': [round(x, 2) for x in error]})

دستور اجرا، نتیجهٔ مورد انتظار و محدودیت این فایل در بخش اجرای مقاله توضیح داده شده است.

4. فایل کامل test_optimize.py

این فایل را با همین نام و بدون حذف سطرها کنار بقیهٔ فایل‌ها ذخیره کنید.

from pathlib import Path
import tempfile
import unittest
from PIL import Image
from optimize import optimize


class ImageTests(unittest.TestCase):
    def test_no_upscaling_or_overwriting(self):
        with tempfile.TemporaryDirectory() as directory:
            source, target = Path(directory) / 'in.png', Path(directory) / 'out.webp'
            Image.new('RGB', (100, 50), 'red').save(source)
            self.assertEqual(optimize(source, target)['width'], 100)
            with self.assertRaises(FileExistsError): optimize(source, target)

    def test_orientation(self):
        with tempfile.TemporaryDirectory() as directory:
            source, target = Path(directory) / 'in.jpg', Path(directory) / 'out.webp'
            image = Image.new('RGB', (50, 100), 'blue')
            exif = Image.Exif(); exif[274] = 6; image.save(source, exif=exif)
            result = optimize(source, target)
            self.assertEqual((result['width'], result['height']), (100, 50))


if __name__ == '__main__': unittest.main()

دستور اجرا، نتیجهٔ مورد انتظار و محدودیت این فایل در بخش اجرای مقاله توضیح داده شده است.

اجرای مقایسهٔ کیفیت و انتخاب خروجی

پس از انتخاب خروجی، اندازه‌های واکنش‌گرا، عرض و ارتفاع ذاتی و اولویت بارگذاری تصویر اصلی را هم تنظیم کنید. هیچ‌کدام از این کارها به‌تنهایی رتبهٔ جست‌وجو را تضمین نمی‌کند، اما می‌تواند خوانایی و تجربهٔ صفحه را بهتر کند. اصل عکس را نگه دارید، روی نسخهٔ کپی آزمایش کنید و نتیجه را با همان موضوع و اندازهٔ واقعی مقایسه کنید. تصمیم خوب، کیفیت قابل قبول با حجم اندازه‌گیری‌شده است، نه بیشترین عدد کیفیت در تنظیمات.

چک‌لیست اجرای عملی

  • برای عکس تار، اصل باکیفیت‌تر جست‌وجو شود.
  • جهت دوربین پیش از تغییر اندازه اصلاح شود.
  • حجم و نتیجهٔ دیداری هم‌زمان مقایسه شوند.
  • اصل فایل و خروجی موجود بازنویسی نشوند.

منابع

تدوین و راهنمای عملی: دانشنامه لیان.