لماذا يكبر حجم الصورة بعد الضغط؟ ثلاثة أسباب
أداة الضغط لديك سليمة على الأرجح. فحين لا يعمل ضغط الصور كما ينبغي، أي يعود الملف بالحجم نفسه أو أكبر بوضوح مما أدخلته، يكون السبب في الغالب واحداً من ثلاثة، ولا واحد منها عطل في الأداة.
السبب الأول، وهو الأشيع: أي أداة تضغط عبر عنصر canvas في المتصفح تتخلّص من نوع الألوان (color type) الخاص بملف PNG وتعيد ترميز كل بكسل بصيغة RGBA بـ 32 بت. مرّرنا جميع بطاقات Open Graph في هذا الموقع، وعددها 83 ملف PNG، عبر canvas.toBlob('image/png') في Chrome 151.0.0.0. عادت الـ 83 كلها أكبر. وكان وسيط الزيادة +76.6%، وأصغرها +35.6%، وأكبرها +237.2%.
والسبب الثاني: ملف JPEG لديك مضغوط أصلاً. مرّره خمس جولات عند جودة 0.8 وسترى الحجم يتوقّف عن التغيّر بعد الجولة الثانية، بينما تواصل كل جولة إتلاف الصورة.
والثالث: ملف PNG لديك سبق أن خضع للتكميم (quantization). لم يبقَ فيه فائض ألوان تحذفه جولة ثانية.
ولا يُصلح أياً من هذه سحبُ مؤشّر الجودة إلى الأسفل. الحل صيغة مختلفة أو محرّك ترميز مختلف. فالـ 83 ملف PNG نفسها، حين حُوّلت إلى WebP عند جودة 0.8، صغرت كلها بوسيط −94.2%.
كيف أُنتجت هذه الأرقام. Chrome 151.0.0.0 مُدار عبر Playwright، وmacOS 26.5.2، وNode v25.8.2، وupng-js 2.1.0، وImageMagick. ومجموعة الـ 83 ملفاً هي كل ملف PNG في مجلّد
public/ogبهذا الموقع، لا عيّنة منه. وأربعة ملفات إضافية مضبوطة بمقاس 1200×630 و800×600 تغطّي حالات الصورة الفوتوغرافية والرسم ولوحة الألوان وJPEG كلاً على حدة.
1. ضغط الصور لا يعمل: فرز في ثلاثين ثانية
ابحث عن صفّك في الجدول، ثم اقرأ القسم الذي يشير إليه.
| ما أدخلته | ما حصلت عليه | السبب الجذري | اقرأ |
|---|---|---|---|
| لقطة شاشة أو رسم بصيغة PNG | أكبر من الأصل | أعاد canvas ترميزه إلى RGBA بـ 32 بت | القسم 2 |
| صورة فوتوغرافية JPEG | أكبر من الأصل | حُفظت بصيغة PNG | القسم 2 |
| صورة فوتوغرافية JPEG | تغيّر يكاد لا يُذكر | بلغت هضبة الحجم أصلاً | القسم 3 |
| ملف PNG سبق أن مرّ بأداة ضغط | لا تغيّر، أو أكبر قليلاً | لم يبقَ هامش | القسم 4 |
| أي صورة | أصغر، لكنها باهتة أو مختلفة الألوان | فقد الأجيال، أو تجريد ملفّ تعريف ICC | القسمان 3 و6 |
والصفوف لا يستبعد بعضها بعضاً. فصورة JPEG تُلقى في مُصدِّر PNG قائم على canvas تصيب الصفّين الأولين معاً، وهكذا يتحوّل ملف حجمه 47,828 بايت إلى ملف حجمه 809,415 بايت.
وإن أردت تخطّي التشخيص والحصول على ملف أصغر فحسب، فإن أداة ضغط الصور لدينا تشغّل محرّك ترميز PNG مُكمِّماً بدل رحلة الذهاب والإياب عبر canvas، وتتخلّص من ناتجها هي نفسه كلما لم تكن النتيجة أصغر مما رفعته.
2. السبب الأول: canvas في المتصفح يكتب دائماً RGBA بـ 32 بت
ماذا يفعل canvas.toBlob() فعلاً بملف PNG لديك
لا توجد خطوة ضغط في رحلة canvas ذهاباً وإياباً. هناك فكّ ترميز ثم إعادة ترميز، وكل ما ليس قيمة بكسل يسقط بينهما.
ارسم صورة على canvas، فيفكّ المتصفح ترميزها إلى مخزن RGBA مسطّح: أربعة بايتات لكل بكسل، بلا لوحة ألوان، وبلا حيل في عمق البتات، وبلا بيانات وصفية. ثم يعيد HTMLCanvasElement.toBlob() ترميز ذلك المخزن من الصفر. وتُعرّف مواصفة PNG ستة أنواع ألوان، ولمحرّك ترميز PNG مطلق الحرية في اختيار أرخصها تمثيلاً للصورة. أما محرّك canvas في Chrome فلا يختار. إنه يُخرج دائماً color type 6.
| نوع الألوان الداخل | ما يعيده canvas.toBlob('image/png') |
|---|---|
| RGB (color type 2) | RGBA (color type 6) |
| لوحة ألوان (color type 3) | RGBA (color type 6) |
| JPEG (لا يملك نوع ألوان PNG) | RGBA (color type 6) |
ويمكنك قراءة ذلك من البايتات مباشرة. فالبايت 25 من ملف PNG هو عمق البتات، والبايت 26 هو نوع الألوان، وكلاهما داخل مقطع IHDR:
xxd -s 24 -l 2 -p suspect.png
# 0806 -> عمق البتات 8، نوع الألوان 6 (RGBA)
أنتجت أنواع المُدخلات الثلاثة أعلاه كلها depth=8 type=6. فصورة بلوحة من 64 لوناً تخزّن بايتاً واحداً لكل بكسل مع جدول صغير؛ وبعد الرحلة صارت تخزّن أربعة بايتات لكل بكسل، والجدول اختفى. ويستردّ deflate بعضاً من ذلك، لا كلّه أبداً.
أعِد إنتاج الأمر في متصفحك أنت بمقطع وحدة التحكّم هذا. يختار ملفاً، ويمرّره ذهاباً وإياباً، ويطبع الحجمين ونوع الألوان:
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/png,image/jpeg';
input.onchange = async () => {
const file = input.files[0];
const bitmap = await createImageBitmap(file);
const canvas = document.createElement('canvas');
canvas.width = bitmap.width;
canvas.height = bitmap.height;
canvas.getContext('2d').drawImage(bitmap, 0, 0);
const blob = await new Promise((r) => canvas.toBlob(r, 'image/png'));
const head = new Uint8Array(await blob.slice(0, 26).arrayBuffer());
console.log(file.name, file.size, '->', blob.size);
console.log('bit depth', head[24], 'color type', head[25]);
};
input.click();
ومعامل الجودة المُمرَّر إلى toBlob يُتجاهَل مع image/png. فـPNG غير فاقد، فليس ثمّة ما يقايض به رقم الجودة، والمؤشّر الذي يبدو أنه يتحكّم في ضغط PNG داخل أداة قائمة على canvas لا يتحكّم في شيء.
قياس 83 ملفاً: كل صورة مضغوطة عادت أكبر من الأصل
وهذه نتيجة التشغيل كاملة، بلا انتقاء:
| القياس | القيمة |
|---|---|
| الملفات المختبَرة | 83 (كل ملف PNG في public/og، بنوعَي الألوان RGB ولوحة الألوان) |
| الملفات التي كبرت | 83 / 83 (100%) |
| أصغر زيادة | +35.6% |
| وسيط الزيادة | +76.6% |
| أكبر زيادة | +237.2% |
وملف واحد لتتبيّن شكل المسألة: aes-decrypt.png انتقل من 505,516 بايت إلى 898,014 بايت، بزيادة +77.6%. والملف نفسه مرمَّزاً بصيغة WebP عند جودة 0.8 يبلغ 27,188 بايت، أي −94.6%.
وللعيّنة حدودها. فهذه الملفات الـ 83 بطاقات Open Graph: مقاس 1200×630، وخلفيات مسطّحة، ونصّ كبير، وحفنة من ألوان العلامة. وهي بالضبط نوع الرسوم الذي يجيد محرّك ترميز PNG جيّد التعامل معه، ولهذا تحديداً تؤذيها رحلة canvas إلى هذا الحدّ. هذه نتيجة قوية لملفات PNG ذات الطابع الرسومي، وليست ادّعاءً بأن كل ملف PNG في العالم يكبر بعد رحلة canvas. فملف PNG فوتوغرافي مخزَّن أصلاً بصيغة RGBA كاملة لديه ما يخسره أقلّ بكثير.
ما تثبته النتيجة: إذا كانت صورتك المضغوطة أكبر من الأصل وكانت الأداة تعمل داخل تبويب متصفح، فمحرّك الترميز هو أول ما ينبغي فحصه، لا إعداداتك.
لماذا يكبر ملف JPEG محفوظ بصيغة PNG بمقدار 16.9 ضعفاً
أربعة ملفات مضبوطة، مرّت أربعتها بالمسار نفسه عبر canvas:
| الملف | الطابع | الأصل | PNG عبر canvas | التغيّر | JPEG q92 | JPEG q80 | WebP q80 |
|---|---|---|---|---|---|---|---|
og-a.png | رسم، 2,351 لوناً، RGB | 306,302 | 607,481 | +98.3% | 56,459 | 37,695 | 13,852 |
quantized.png | لوحة من 64 لوناً | 59,843 | 184,856 | +208.9% | 78,388 | 44,442 | 16,046 |
photo.png | صورة فوتوغرافية، 479,373 لوناً | 498,639 | 867,763 | +74.0% | 77,321 | 45,050 | 24,068 |
photo.jpg | JPEG q82 | 47,828 | 809,415 | +1,592% (16.9×) | 58,762 (+22.9%) | 47,152 | 23,862 |
ذلك الصفّ الأخير: 47,828 بايت داخلاً، و809,415 بايت خارجاً.
والآلية تفسّر عائلة كاملة من البلاغات التي تقول «الضغط زاد الحجم». فـJPEG مرمّز فاقد يعمل في نطاق التردّد: يحوّل كتلاً بمقاس 8×8 إلى معاملات DCT، ويكمّمها بعنف، ويخزّن الناجي منها. أما PNG فمرمّز غير فاقد يعمل في النطاق المكاني: يتنبّأ بكل بكسل من جيرانه ثم يضغط البواقي بـdeflate. فُكّ ترميز ملف JPEG وستحصل على بكسلات تحمل كل أثر أدخله المكمِّم: رنيناً قرب الحواف، وتكتّلاً في التدرّجات الناعمة، وضجيجاً خفيفاً حيث لم يكن في الأصل شيء.
احفظ تلك البكسلات بصيغة PNG، فأنت تطلب من مرمّز غير فاقد أن يخزّن الآثار بدقّة تامّة. وهو يستجيب. فالضجيج نفسه الذي أنشأه JPEG ليصغّر الملف صار هو ما يضخّم ملف PNG، لأن الضجيج هو بالضبط ما يعجز مرمّز تنبّؤي غير فاقد عن ضغطه.
وحيثما وقف خيار افتراضي اسمه «حفظ بصيغة PNG» أمام صورة فوتوغرافية، فهذا ما يجري: أدوات لقطات الشاشة، ومخرجات برامج التصميم، وتطبيقات المحادثة، وبعض أدوات الرفع.
البكسلات نفسها، بفارق 3.46 أضعاف، وكلاهما غير فاقد
محرّك ترميز canvas ضعيف بمقدار قابل للقياس. خذ مخزن البكسلات من og-a.png ورمّزه بطريقتين، كلتاهما غير فاقدة تماماً:
| محرّك الترميز | الناتج | نوع الألوان |
|---|---|---|
canvas في Chrome عبر toBlob('image/png') | 607,481 بايت | RGBA (رفع قسري) |
upng-js عبر encode(..., cnum=0) | 175,491 بايت | RGB (احتفاظ بنوع الألوان الأصلي) |
3.46×، لبكسلات متطابقة. وقد تحقّقنا من دعوى انعدام الفقد بدل افتراضها: فكّ ترميز ناتج upng-js عند cnum=0 يعيد مخزناً مطابقاً بايتاً ببايت لمخزن RGBA المُدخل. ولم نخسر شيئاً مقابل ذلك الفارق البالغ 3.46×.
يفسّر هذا لماذا تعطي أداتا ضغط على الإنترنت، كلتاهما مجانية وكلتاهما تعلن الشيء نفسه، نتائج لا تكاد تُقارَن. فعبارة «الضغط داخل المتصفح» تصف تنفيذين مختلفين تماماً. أحدهما يسلّم البكسلات إلى canvas.toBlob ويشحن ما يعود منه أياً كان. والآخر يحمل محرّك ترميز PNG حقيقياً ويتحكّم في نوع الألوان. المُدخل نفسه، والمتصفح نفسه، وفارق 3.46×.
3. السبب الثاني: لم يبقَ في ملف JPEG لديك ما يُعطيه
خمس جولات إعادة ضغط، والحجم يتوقّف عن الحركة
خذ photo.jpg المحفوظ أصلاً عند جودة 82، وأعد ضغطه عند جودة 0.8 خمس مرات متتالية، يغذّي كل جيل الجيل الذي يليه:
| الجيل | البايتات | مقابل السابق |
|---|---|---|
| 0 (الأصل، q82) | 47,828 | — |
| 1 | 47,152 | −1.4% |
| 2 | 47,164 | +0.0% |
| 3 | 47,156 | −0.0% |
| 4 | 47,156 | 0.0% |
| 5 | 47,156 | 0.0% |
الجولة الأولى تشتري لك 1.4%. ومن الجيل 2 فصاعداً يصبح حجم الملف محبوساً داخل نطاق ±12 بايت، والجيلان 4 و5 مطابقان في الحجم للجيل 3 تماماً.
هكذا يبدو «الضغط لا يعمل» حين يكون المُدخل ملف JPEG. الأداة عملت. ومحرّك الترميز عمل. لكن لم يبقَ ما يُحذف، لأن جداول التكميم عند الجودة 80 كانت أصلاً تصفّر تقريباً المعاملات نفسها التي أبقتها الجودة 82. وحين يذهب معامل، لا يمكن حذفه مرّة أخرى.
يمكنك أن تراقب ذلك يحدث محلياً:
cp photo.jpg gen0.jpg
for i in 1 2 3 4 5; do
magick "gen$((i-1)).jpg" -quality 80 "gen$i.jpg"
done
stat -f%z gen0.jpg gen1.jpg gen2.jpg gen3.jpg gen4.jpg gen5.jpg
وعلى لينكس استخدم stat -c%s بدلاً منه. وأعداد البايتات الدقيقة تتوقّف على محرّك الترميز الذي ترتبط به نسخة ImageMagick لديك، فلا تتوقّع أن يتكرّر الجدول أعلاه رقماً برقم. المقصود هو الشكل العام: هبوط واحد ذو معنى، ثم خطّ مسطّح.
رفع رقم الجودة ليس ضغطاً
انظر مجدّداً إلى صفّ photo.jpg في القسم 2. إعادة ضغط ذلك الأصل ذي الجودة 82 عند جودة 92 أنتجت 58,762 بايت: +22.9%.
وهذا يفاجئ من يظنّون أن معامل الجودة قرص يدور من «صغير» إلى «كبير» لهم أن يضبطوه حيث شاؤوا. إنه ليس هدف جودة مطلقاً. إنه يختار جدول تكميم، وتمرير صورة مفكوكة الترميز عبر جدول أدقّ من الجدول الذي أنتجها يخزّن الآثار الموجودة بدقّة أعلى ويضيف فوقها جولة فقد جديدة. ملف أكبر وصورة أسوأ، في الوقت نفسه.
والقاعدة: لا تعد أبداً ضغط ملف JPEG عند إعداد جودة أعلى من الإعداد الذي حُفظ به. وإن كنت لا تعرف ما كان ذلك الإعداد، فلا تعد ضغطه أصلاً. ارجع إلى المصدر.
فقد الأجيال: خسارة الجودة في إعادة ضغط JPEG التي لا تراها
في جدول الهضبة فخّ. فالحجم توقّف عن التغيّر بعد الجيل 2، لكن الصورة ظلّت تتغيّر. فكل جولة تفكّ الترميز إلى بكسلات، ثم تعيد التحويل، ثم تعيد التكميم. والمعاملات التي نجت على حافة عتبة في جيل تُدفع عبرها في الجيل التالي.
الضرر لا يظهر حيث تبحث عنه. فبحجم الصورة المصغَّرة، لا فرق بين الجيل 5 والجيل 1. كبّر إلى 100% وافحص المواضع التي يفشل فيها JPEG أولاً دائماً: الحواف الحادّة على خلفيات مسطّحة، والنصّ، والتدرّجات الناعمة حيث يظهر التكتّل على شكل مربّعات 8×8 مرئية. وفي خطّ بناء يعيد الضغط عند كل نشر، يتراكم هذا بصمت شهوراً.
وقد قسنا هنا أحجام الملفات لا الجودة المُدرَكة، فلن نسوق لك رقم PSNR أو SSIM للأجيال الخمسة. لم نقس أياً منهما. وما تثبته بيانات الحجم وحدها يكفي: من الجيل 2 فصاعداً تقع الكلفة كلها على جانب الجودة والعائد صفر.
وانزياح الألوان عطل منفصل لكن بالمُطلِق نفسه. فـcanvas لا يحمل بيانات وصفية، ولذا تُسقط رحلة toBlob كتلة EXIF وملفّ تعريف ICC معها. فصورة موسومة بـDisplay P3 أو Adobe RGB تدخل، ثم تخرج بلا وسم، فيفسّرها العارضون على أنها sRGB. قيم البكسلات لم تتحرّك. ما تحرّك هو تعليمات تفسيرها.
4. السبب الثالث: حجم ملف PNG لا ينخفض لأنه كُمِّم من قبل
إن كان ملف PNG لديك قد مرّ بأداة ضغط مرّة، فلن تجد الجولة الثانية ما تعمل عليه. وهذا هو صفّ quantized.png: ملف حجمه 59,843 بايت مُختزَل أصلاً إلى لوحة من 64 لوناً، أخضعناه لثلاثة مسارات منفصلة:
| المسار | النتيجة | مقابل الأصل |
|---|---|---|
إعادة ترميز غير فاقدة (upng عند cnum=0) | 61,377 | +2.6% |
| تكميم إلى 256 لوناً | 61,366 | +2.5% |
| تكميم إلى 64 لوناً | 61,366 | +2.5% |
خرج كل مسار أكبر من الأصل. ليس بفارق كبير، لكنه أكبر، ويشمل ذلك تكميم ملف يملك 64 لوناً أصلاً إلى 64 لوناً.
يعمل ضغط PNG بإزالة الفائض: ألوان متكرّرة، وجيران يمكن التنبّؤ بهم، ولوحة ألوان صغيرة. وقد جمعت جولة سابقة ذلك كله. وما تبقّى يكاد لا يقبل الضغط، والزيادة الضئيلة هي حِمل محرّك الترميز نفسه: ترتيب مختلف قليلاً للوحة الألوان، واختيارات مرشّحات مختلفة لكل سطر مسح، وحظّ أقلّ بقليل مع deflate.
والنتيجة العملية أن «توفير 0%» على ملف PNG مُحسَّن سلفاً هو النتيجة الصحيحة، لا عطلاً. فالأداة التي تبلّغ بزيادة طفيفة ثم تُبقي ملفك الأصلي تتصرّف تصرّفاً سليماً. أما التي تسلّمك الملف الأكبر رغم ذلك فلا.
5. التكميم هو ما يختصر ملف PNG، لا إعادة الترميز
إعادة الترميز غير الفاقدة مقابل 256 لوناً مقابل 64 لوناً
ثلاثة ملفات، وثلاث استراتيجيات، بالقياس:
| الملف | الأصل | غير فاقدة (cnum=0) | 256 لوناً | 64 لوناً |
|---|---|---|---|---|
og-a.png | 306,302 | 175,491 (−42.7%) | 110,772 (−63.8%) | 76,230 (−75.1%) |
photo.png | 498,639 | 587,863 (+17.9%) | 109,284 (−78.1%) | 61,397 (−87.7%) |
quantized.png | 59,843 | 61,377 (+2.6%) | 61,366 (+2.5%) | 61,366 (+2.5%) |
يبرز أمران.
إعادة الترميز غير الفاقدة هي أضعف أداة متاحة. كسبت 42.7% على الرسم، وردّت 17.9% على الصورة الفوتوغرافية، وخسرت 2.6% على الملف المكمَّم سلفاً. فالمحتوى الفوتوغرافي يهزم حتى محرّك ترميز PNG غير فاقد وكفؤاً، لأنه لا توجد لوحة ألوان تُكتشف، ولأن البكسلات المتجاورة لا يتنبّأ بعضها ببعض جيداً.
والتكميم هو موضع الاختزال، والفارق واسع: 63.8% مقابل 42.7% على الرسم نفسه عند 256 لوناً، و75.1% عند 64. وعلى سطر الأوامر:
magick logo.png -colors 64 PNG8:logo-64.png
magick logo.png -colors 256 PNG8:logo-256.png
والبادئة PNG8: تفرض ملف PNG بلوحة ألوان. وبدونها قد يقلّل ImageMagick الألوان ثم يكتب مع ذلك ملفاً بألوان حقيقية، فيضيع معظم الفائدة.
متى تكون لوحة الألوان آمنة، ومتى تحصل على تحزّم لوني
التكميم فاقد. فهو يربط كل بكسل بأقرب مدخل في لوحة ألوان محدودة، والسؤال إذن هو ما إذا كان محتواك يحمل من الألوان المتمايزة ما يكفي ليصبح ذلك مرئياً.
الآمن: الأيقونات، والشعارات، ولقطات واجهات المستخدم، والرسوم التوضيحية المسطّحة، والمخطّطات، وكل ما فيه مساحات واسعة بلون موحّد وحواف حادّة. فهذه تحوي في العادة بضع مئات من الألوان المتمايزة على الأكثر، فتصبح لوحة من 256 مدخلاً شبه مجانية، وكثيراً ما تنجو حتى 64.
والمحفوف بالمخاطر: الصور الفوتوغرافية، والتدرّجات الناعمة، والظلال الخفيفة، والطبقات نصف الشفافة. فاختزال تدرّج إلى 64 درجة ينتج حزماً مرئية، والتذبيب (dithering) يستبدل بتلك الحزم ضجيجاً يكلّفك بدوره بعضاً من حجم الملف مجدّداً. والشفافية الجزئية فوق تدرّج هي أصعب الحالات جميعاً.
ومقدار ما ينجو من الشفافية يتوقّف على محرّك الترميز لا على التكميم ذاته. فبادئة PNG8: في ImageMagick تكتب شفافية ثنائية، فيكون البكسل إما معتماً تماماً أو شفافاً تماماً، وتعود الحافة الناعمة المصقولة حادّة. أما مكمِّم PNG المتخصّص فيحتفظ بقناة alpha كاملة، ومعها الحافة الناعمة. فإن كان أصلك يحمل ظلاً أو حوافّ متدرّجة، فقارن بين الاثنين قبل أن تعتمد أحدهما.
وافحص النتيجة عند 100%، لا في صورة مصغَّرة. فالتحزّم اللوني هو الأثر الوحيد الذي تخفيه المعاينة المصغَّرة إخفاءً موثوقاً.
6. الحلّ: غيّر الصيغة بدل إعادة الضغط
جدول قرار الصيغة
| المحتوى | استخدم | لماذا |
|---|---|---|
| الصور الفوتوغرافية | WebP، أو JPEG لأقصى توافق | الترميز الترددي الفاقد هو ما تحتاجه الصور |
| لقطات الشاشة ورسوم الواجهة | PNG مكمَّم، أو WebP | ألوان مسطّحة وحواف حادّة ولوحات صغيرة |
| الأيقونات والشعارات | SVG حين يكون لديك الملف المتّجه، وإلا فـPNG مكمَّم | المتّجهات لا تعاني مشكلة دقّة |
| كل ما يحتاج شفافية | WebP أو PNG | كلاهما يحمل قناة alpha كاملة |
| الرسوم المتحرّكة | WebP | صيغة واحدة بدل GIF |
| الأرشفة الدقيقة بكسلاً ببكسل | PNG غير فاقد | الحالة الوحيدة التي يكون فيها انعدام الفقد شرطاً |
وهذه النسخة المختصرة عن قصد. فكفاءة الترميز ودعم المتصفحات عبر الصيغ الحديثة لها مقال خاص بها: WebP مقابل AVIF مقابل JPEG.
ماذا فعل WebP بالملفات الـ 83 نفسها
مجموعة الـ 83 ملفاً التي كبرت في 100% من الحالات عبر PNG الصادر من canvas، حين حُوّلت بدلاً من ذلك إلى WebP عند جودة 0.8: صغر 83 من 83، بوسيط −94.2%. والملف المفرد الذي مرّ سابقاً، aes-decrypt.png، انتقل من 505,516 بايت إلى 27,188 بايت، أي −94.6%.
والملفات المضبوطة تتّفق مع ذلك. og-a.png: 306,302 بصيغة PNG، و13,852 بصيغة WebP q80. وphoto.png: 498,639 بصيغة PNG، و24,068 بصيغة WebP q80. وphoto.jpg: 47,828 بصيغة JPEG، و23,862 بصيغة WebP q80.
وتحفّظ واحد على المقارنة الأخيرة. فـWebP عند جودة 0.8 فاقد، فهو لا ينافس PNG بشروط متكافئة، وإعادة ترميز ملف JPEG قائم إلى WebP تكلّفك جيلاً على أي حال. والمقارنة التي تهمّ هي مع ما كنت على وشك شحنه، لا مع أصل مثالي افتراضي.
متى ينبغي أن تبقي على PNG رغم ذلك
انعدام الفقد شرط أحياناً، لا تفضيلاً. أبقِ على PNG للأصول التي تعود إلى خطّ عمل التصميم لتُحرَّر من جديد، وللقطات الشاشة المستخدمة في اختبارات المقارنة البكسلية، ولشرائح الواجهة التي يكسر فيها لون واحد منزاح فرقاً بصرياً، ولكل ما سيُركَّب لاحقاً حيث تتراكم آثار التكميم. وفي هذه الحالات، شغّل محرّك ترميز مكمِّماً لائقاً إن سمح المحتوى بذلك، واقبل حجم الملف إن لم يسمح.
نوع آخر من «الأكبر» لا علاقة له بالضغط
إن ضمّنت الصور بصيغة data URI، فالحجم داخل ملف CSS أو HTML ليس الحجم على القرص. فترميز Base64 يحوّل كل 3 بايتات إلى 4 محارف، إضافة إلى الحشو، فيصبح النصّ أكبر حسابياً بنسبة +33% من البايتات التي يحملها، قبل أي ضغط نقل. فصورة مُحسَّنة تحسيناً تاماً تكبر بمقدار الثلث لحظة تضمينها. ومتى تستحقّ هذه المقايضة أن تُعقد، فذاك ما يغطّيه دليلنا عن تضمين data URI.
7. التنفيذ: المتصفح، وسطر الأوامر، وخطّ البناء
في المتصفح
عند اختيار أداة تعمل في المتصفح، السؤال هو ما إذا كان PNG يمرّ عبر canvas. وفي أداة ضغط الصور لدينا، لا يمرّ. فمُدخل PNG يُكمَّم إلى لوحة ألوان ويُكتب من جديد ملف PNG حقيقياً بقناة alpha سليمة، فتنجو الشفافية والحواف الناعمة؛ ومؤشّر الجودة يقابل حجم لوحة الألوان لا معامل toBlob الذي يتجاهله PNG أصلاً. والجودة 100 تقابل إعادة ترميز غير فاقدة. أما JPEG وWebP فيمرّان فعلاً عبر canvas، حيث يكون معامل الجودة حقيقياً ويفعل ما تتوقّعه.
وثمّة سلوك يبدو عطلاً وليس كذلك: إن لم تكن النتيجة المضغوطة أصغر من الملف الذي رفعته، تطرح الأداة ناتجها هي جانباً وتبقي بايتاتك الأصلية. وعلى ملف PNG مُحسَّن سلفاً سترى «توفير 0%». وهذا هو القسم 4 يعمل كما يُراد له.
والمعالجة تجري في متصفحك، ولا ترفع الأداة ملفك إلى أي خادم.
على سطر الأوامر
يأتي cwebp مع libwebp، وهو أسرع طريقة لاختبار ما إذا كان تغيير الصيغة يحلّ مشكلتك:
# WebP فاقد، الجودة 0-100
cwebp -q 80 photo.png -o photo.webp
# WebP غير فاقد، جهد الضغط 0-9
cwebp -z 9 logo.png -o logo.webp
ويغطّي ImageMagick 7 حالتَي التحويل والتكميم:
# من PNG إلى JPEG عند جودة تختارها
magick photo.png -quality 80 photo.jpg
# التكميم إلى PNG بلوحة من 64 لوناً
magick logo.png -colors 64 PNG8:logo-64.png
# إسقاط EXIF وغيرها من البيانات الوصفية
magick photo.jpg -strip photo-clean.jpg
وعلى macOS، يكون sips مثبَّتاً أصلاً ولا يحتاج أي اعتماديات:
# التحويل إلى JPEG؛ يقبل formatOptions قيماً من 0-100 أو low/normal/high/best
sips -s format jpeg -s formatOptions 80 photo.png --out photo.jpg
# تغيير الحجم بحيث تصبح الحافة الأطول 1200 بكسل، مع الحفاظ على نسبة الأبعاد
sips -Z 1200 photo.jpg --out photo-1200.jpg
# قراءة الأبعاد مجدداً
sips -g pixelWidth -g pixelHeight photo-1200.jpg
غيّر الحجم قبل أن تضغط. فمحرّكات الترميز تعمل على البكسلات، وأرخص بكسل هو البكسل غير الموجود.
داخل خطّ البناء
وحين يصبح هذا آلياً لا يدوياً، ينتقل القرار إلى موضع تنفيذ العمل وإلى المكتبة التي تنفّذه، وهو ما يغيّر المفاضلات: ضغط الصور بين حلول المتصفح وNode.js يغطّي تلك المقارنة. والقاعدة الوحيدة التي تنتقل من هذا المقال: اضغط انطلاقاً من ملف المصدر الأصلي في كل بناء، لا من ناتج البناء السابق أبداً. فهكذا يهبط خطّ البناء بنفسه على منحنى فقد الأجيال بينما تبدو أحجام الملفات مستقرّة تماماً.
8. خمس قناعات لا تدعمها القياسات
«الضغط مرّتين يجعل الملف أصغر.» الجيل 1 اشترى 1.4%. والأجيال من 2 إلى 5 بقيت داخل ±12 بايت بينما ظلّت الصورة تتدهور. الجولة الثانية كلفة خالصة.
«PNG غير فاقد، فهو الصيغة الأفضل.» انعدام الفقد خاصية لا فضيلة. فصورتنا الاختبارية 498,639 بايت بصيغة PNG و24,068 بصيغة WebP q80. والحفاظ على صورة فوتوغرافية بدقّة البتّ الواحد وهي لن تُشاهَد إلا على شاشة لا يشتري شيئاً ويكلّف معظم الملف.
«الجودة 100 هي الخيار الآمن.» إعادة ضغط ملف JPEG بجودة 82 عند جودة 92 أنتجت +22.9% وصورة أسوأ. فوق إعداد جودة الأصل، يكفّ الرقم عن أن يعني «أكثر أماناً» ويبدأ في أن يعني «أكبر».
«الملف كبير لأن الدقّة عالية.» الدقّة مهمّة، لكن الصيغة أهمّ عند الدقّة نفسها. فـog-a.png مقاسه 1200×630 في الحالتين: 607,481 بايت بصيغة PNG الصادر من canvas، و13,852 بايت بصيغة WebP q80. عدد البكسلات نفسه.
«أدوات الضغط على الإنترنت كلها سواء.» البكسلات نفسها، والمتصفح نفسه، وكلاهما غير فاقد: 607,481 بايت من canvas، و175,491 من upng-js. فارق 3.46× بين أداتين تصفان نفسيهما الوصف ذاته.
9. الأسئلة الشائعة
لماذا صورتي المضغوطة أكبر من الأصل؟
لأن الأداة أعادت ترميزها بدل أن تضغطها. فناتج canvas في المتصفح هو دائماً PNG بصيغة RGBA بـ 32 بت، وهو يتخلّص من لوحات الألوان ويخزّن أربعة بايتات لكل بكسل. وفي اختبارنا لـ 83 ملف PNG حقيقياً، كبرت الـ 83 كلها، بوسيط زيادة +76.6%. حوّل إلى WebP أو JPEG بدلاً من ذلك.
لماذا لا يصغر ملف PNG لديّ حين أضغطه؟
صيغة PNG غير فاقدة، فليس لدى مؤشّر الجودة ما يقايض به. والاختزال يأتي من خفض عدد الألوان، وإن كان الملف مكمَّماً أصلاً فلا يبقى ما يُخفَض. فملف PNG الاختباري ذو الـ 64 لوناً عاد بزيادة +2.5% بعد تكميمه إلى 64 لوناً مرّة ثانية.
هل يفقد ضغط JPEG مرّتين شيئاً من الجودة؟
نعم، ولا تحصل في المقابل على شيء تقريباً. خمس جولات عند جودة 0.8: من 47,828 بايت إلى 47,152 في الجولة الأولى، ثم حبس داخل 12 بايت في الجولات الأربع التالية. توقّف الحجم عن الحركة بينما ظلّت كل جولة تعيد تكميم الصورة. احتفظ بأصولك.
هل أستخدم PNG أم JPEG لتصغير الملف؟
للصور الفوتوغرافية: JPEG أو WebP، بلا استثناء. فصورتنا الاختبارية قيست عند 498,639 بايت بصيغة PNG، و45,050 بصيغة JPEG q80، و24,068 بصيغة WebP q80. واحفظ PNG للرسوم المسطّحة والنصّ الحادّ والشفافية، حيث تقوم لوحة ألوان صغيرة بعمل الضغط.
لماذا أصبحت صورتي ضبابية بعد الضغط؟
سببان مختلفان. محرّكات الترميز الفاقدة عند إعدادات جودة منخفضة تنتج تكتّلاً مرئياً حول الحواف والنصّ. والجولات المتكرّرة تضيف فقد أجيال حتى حين يتوقّف حجم الملف عن التغيّر. وإن كانت الألوان قد انزاحت بدل أن تنعم، فرحلة canvas أسقطت ملفّ تعريف ICC لديك، لأن canvas لا يحمل بيانات وصفية على الإطلاق.
هل يمكنني ضغط صورة دون فقدان الجودة؟
نعم، لكن توقّع مكسباً أقلّ بكثير. فإعادة الترميز غير الفاقدة لا تفعل سوى إعادة كتابة البكسلات ذاتها بكفاءة أعلى: من 306,302 إلى 175,491 بايت على الرسم لدينا، مع تحقّق من التطابق بايتاً ببايت بعد فكّ الترميز. والأسلوب نفسه على صورة فوتوغرافية سار في الاتجاه الخطأ، +17.9%. وللتوفير الملموس بلا فقد مرئي، استخدم WebP عند جودة 80.
لماذا ملف PNG لديّ كبير إلى هذا الحدّ وهو مجرّد لقطة شاشة؟
تُخزَّن لقطات الشاشة بصيغة PNG بـRGBA كاملة، أربعة بايتات لكل بكسل قبل الضغط، وشاشة Retina تضاعف عدد البكسلات في كل بُعد. والمحتوى المسطّح يستجيب جيداً لاختزال لوحة الألوان: فرسم بطاقة Open Graph لدينا انخفض 63.8% عند 256 لوناً و75.1% عند 64.
هل يقلّل تغيير الحجم حجمَ الملف أكثر من الضغط؟
في العادة نعم، والاثنان يتراكبان. فتنصيف البُعدين يزيل ثلاثة أرباع البكسلات قبل أن يبدأ محرّك الترميز، وحجم الملف يتبع عدد البكسلات في العموم. غيّر الحجم أولاً إلى الأبعاد التي تعرضها فعلاً، ثم اضغط مرّة واحدة. فملف كاميرا بدقّته الكاملة يُلقى في خانة صورة مصغَّرة يهدر الجولتين.